-
Notifications
You must be signed in to change notification settings - Fork 7.6k
[Review only] Harden Live Development startup #3142
Changes from 5 commits
31a9ef3
4b00554
e9fd9e0
8379fa2
5b2dc4e
7d08b85
314b29f
5488286
07d46d8
f66491c
3cf56a0
c1cb696
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,8 @@ define(function LiveDevelopment(require, exports, module) { | |
var STATUS_ACTIVE = exports.STATUS_ACTIVE = 3; | ||
var STATUS_OUT_OF_SYNC = exports.STATUS_OUT_OF_SYNC = 4; | ||
|
||
var Dialogs = require("widgets/Dialogs"), | ||
var Async = require("utils/Async"), | ||
Dialogs = require("widgets/Dialogs"), | ||
DocumentManager = require("document/DocumentManager"), | ||
EditorManager = require("editor/EditorManager"), | ||
FileUtils = require("file/FileUtils"), | ||
|
@@ -302,18 +303,18 @@ define(function LiveDevelopment(require, exports, module) { | |
|
||
/** Open a live document | ||
* @param {Document} source document to open | ||
* @return {jQuery.Promise} A promise that is resolved once the live | ||
* document is open | ||
*/ | ||
function _openDocument(doc, editor) { | ||
_closeDocument(); | ||
_liveDocument = _createDocument(doc, editor); | ||
|
||
// Gather related CSS documents. | ||
// FUTURE: Gather related JS documents as well. | ||
_relatedDocuments = []; | ||
agents.css.getStylesheetURLs().forEach(function (url) { | ||
// FUTURE: when we get truly async file handling, we might need to prevent other | ||
// stuff from happening while we wait to add these listeners | ||
|
||
function createLiveStylesheet(url) { | ||
var stylesheetDeferred = $.Deferred(); | ||
|
||
DocumentManager.getDocumentForPath(_urlToPath(url)) | ||
.fail(function () { | ||
stylesheetDeferred.reject(); | ||
}) | ||
.done(function (doc) { | ||
if (!_liveDocument || (doc !== _liveDocument.doc)) { | ||
_setDocInfo(doc); | ||
|
@@ -323,8 +324,21 @@ define(function LiveDevelopment(require, exports, module) { | |
$(liveDoc).on("deleted", _handleRelatedDocumentDeleted); | ||
} | ||
} | ||
stylesheetDeferred.resolve(); | ||
}); | ||
}); | ||
return stylesheetDeferred.promise(); | ||
} | ||
|
||
_closeDocument(); | ||
_liveDocument = _createDocument(doc, editor); | ||
|
||
// Gather related CSS documents. | ||
// FUTURE: Gather related JS documents as well. | ||
_relatedDocuments = []; | ||
|
||
return Async.doInParallel(agents.css.getStylesheetURLs(), | ||
createLiveStylesheet, | ||
false); // don't fail fast | ||
} | ||
|
||
/** Unload the agents */ | ||
|
@@ -423,11 +437,14 @@ define(function LiveDevelopment(require, exports, module) { | |
var editor = EditorManager.getCurrentFullEditor(), | ||
status = STATUS_ACTIVE; | ||
|
||
_openDocument(doc, editor); | ||
if (doc.isDirty && _classForDocument(doc) !== CSSDocument) { | ||
status = STATUS_OUT_OF_SYNC; | ||
} | ||
_setStatus(status); | ||
_openDocument(doc, editor) | ||
.fail(_closeDocument) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the status be set in the fail case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe. It wasn't obvious what the behavior should be in the failure case. On second thought, I think instead the |
||
.done(function () { | ||
if (doc.isDirty && _classForDocument(doc) !== CSSDocument) { | ||
status = STATUS_OUT_OF_SYNC; | ||
} | ||
_setStatus(status); | ||
}); | ||
} | ||
|
||
/** Triggered by Inspector.detached */ | ||
|
@@ -437,24 +454,6 @@ define(function LiveDevelopment(require, exports, module) { | |
// However, the link refers to the Chrome Extension API, it may not apply 100% to the Inspector API | ||
} | ||
|
||
/** Triggered by Inspector.connect */ | ||
function _onConnect(event) { | ||
$(Inspector.Inspector).on("detached", _onDetached); | ||
|
||
// Load agents | ||
_setStatus(STATUS_LOADING_AGENTS); | ||
var promises = loadAgents(); | ||
$.when.apply(undefined, promises).done(_onLoad).fail(_onError); | ||
|
||
// Load the right document (some agents are waiting for the page's load event) | ||
var doc = _getCurrentDocument(); | ||
if (doc) { | ||
Inspector.Page.navigate(doc.root.url); | ||
} else { | ||
Inspector.Page.reload(); | ||
} | ||
} | ||
|
||
/** Triggered by Inspector.disconnect */ | ||
function _onDisconnect(event) { | ||
$(Inspector.Inspector).off("detached", _onDetached); | ||
|
@@ -515,10 +514,11 @@ define(function LiveDevelopment(require, exports, module) { | |
// helper function that actually does the launch once we are sure we have | ||
// a doc and the server for that doc is up and running. | ||
function doLaunchAfterServerReady() { | ||
var url = doc.root.url; | ||
var targetUrl = doc.root.url; | ||
var interstitialUrl = launcherUrl + "?" + encodeURIComponent(targetUrl); | ||
|
||
_setStatus(STATUS_CONNECTING); | ||
Inspector.connectToURL(url).done(result.resolve).fail(function onConnectFail(err) { | ||
Inspector.connectToURL(interstitialUrl).done(result.resolve).fail(function onConnectFail(err) { | ||
if (err === "CANCEL") { | ||
result.reject(err); | ||
return; | ||
|
@@ -555,10 +555,8 @@ define(function LiveDevelopment(require, exports, module) { | |
retryCount++; | ||
|
||
if (!browserStarted && exports.status !== STATUS_ERROR) { | ||
url = launcherUrl + "?" + encodeURIComponent(url); | ||
|
||
NativeApp.openLiveBrowser( | ||
url, | ||
interstitialUrl, | ||
true // enable remote debugging | ||
) | ||
.done(function () { | ||
|
@@ -591,7 +589,7 @@ define(function LiveDevelopment(require, exports, module) { | |
|
||
if (exports.status !== STATUS_ERROR) { | ||
window.setTimeout(function retryConnect() { | ||
Inspector.connectToURL(url).done(result.resolve).fail(onConnectFail); | ||
Inspector.connectToURL(interstitialUrl).done(result.resolve).fail(onConnectFail); | ||
}, 500); | ||
} | ||
}); | ||
|
@@ -658,11 +656,81 @@ define(function LiveDevelopment(require, exports, module) { | |
agents.highlight.redraw(); | ||
} | ||
} | ||
|
||
/** Triggered by Inspector.connect */ | ||
function _onConnect(event) { | ||
|
||
/* | ||
* Create a promise that resolves when the interstitial page has | ||
* finished loading. | ||
* | ||
* @return {jQuery.Promise} | ||
*/ | ||
function waitForInterstitialPageLoad() { | ||
var deferred = $.Deferred(), | ||
keepPolling = true, | ||
timer = window.setTimeout(function () { | ||
keepPolling = false; | ||
deferred.reject(); | ||
}, 10000); // 10 seconds | ||
|
||
/* | ||
* Asynchronously check to see if the interstitial page has | ||
* finished loading; if not, check again until timing out. | ||
*/ | ||
function pollInterstitialPage() { | ||
if (keepPolling && Inspector.connected()) { | ||
Inspector.Runtime.evaluate("window.isBracketsLiveDevelopmentInterstitialPageLoaded", function (response) { | ||
var result = response.result; | ||
|
||
if (result.type === "boolean" && result.value) { | ||
window.clearTimeout(timer); | ||
deferred.resolve(); | ||
} else { | ||
pollInterstitialPage(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we do this on a short |
||
} | ||
}); | ||
} else { | ||
deferred.reject(); | ||
} | ||
} | ||
|
||
pollInterstitialPage(); | ||
return deferred.promise(); | ||
} | ||
|
||
/* | ||
* Load agents and navigate to the target document once the | ||
* interstitial page has finished loading. | ||
*/ | ||
function onInterstitialPageLoad() { | ||
// Load agents | ||
_setStatus(STATUS_LOADING_AGENTS); | ||
var promises = loadAgents(); | ||
$.when.apply(undefined, promises).done(_onLoad).fail(_onError); | ||
|
||
// Load the right document (some agents are waiting for the page's load event) | ||
var doc = _getCurrentDocument(); | ||
if (doc) { | ||
Inspector.Page.navigate(doc.root.url); | ||
} else { | ||
close(); | ||
} | ||
} | ||
|
||
$(Inspector.Inspector).on("detached", _onDetached); | ||
|
||
var interstitialPageLoad = waitForInterstitialPageLoad(); | ||
interstitialPageLoad.fail(close); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like we should show the "connection failed" dialog in this case. |
||
interstitialPageLoad.done(onInterstitialPageLoad); | ||
} | ||
|
||
/** Triggered by a document change from the DocumentManager */ | ||
function _onDocumentChange() { | ||
var doc = _getCurrentDocument(), | ||
status = STATUS_ACTIVE; | ||
status = STATUS_ACTIVE, | ||
promise; | ||
|
||
if (!doc) { | ||
return; | ||
} | ||
|
@@ -672,18 +740,24 @@ define(function LiveDevelopment(require, exports, module) { | |
if (agents.network && agents.network.wasURLRequested(doc.url)) { | ||
_closeDocument(); | ||
var editor = EditorManager.getCurrentFullEditor(); | ||
_openDocument(doc, editor); | ||
promise = _openDocument(doc, editor); | ||
} else { | ||
if (exports.config.experimental || _isHtmlFileExt(doc.extension)) { | ||
close(); | ||
window.setTimeout(open); | ||
promise = open(); | ||
} else { | ||
promise = $.Deferred().resolve(); | ||
} | ||
} | ||
|
||
if (doc.isDirty && _classForDocument(doc) !== CSSDocument) { | ||
status = STATUS_OUT_OF_SYNC; | ||
} | ||
_setStatus(status); | ||
promise | ||
.fail(close) | ||
.done(function () { | ||
if (doc.isDirty && _classForDocument(doc) !== CSSDocument) { | ||
status = STATUS_OUT_OF_SYNC; | ||
} | ||
_setStatus(status); | ||
}); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,18 +29,10 @@ | |
<script type="application/javascript"> | ||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ | ||
/*global window: true */ | ||
|
||
(function () { | ||
"use strict"; | ||
|
||
if (!window.location.search) { | ||
return; | ||
} | ||
var url = decodeURIComponent(window.location.search.slice(1)); | ||
window.setTimeout(function () { | ||
window.location.href = url; | ||
}, 2500); | ||
}()); | ||
|
||
function handleLoad () { | ||
window.isBracketsLiveDevelopmentInterstitialPageLoaded = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this would be necessary, but would there be value in injecting a unique ID into this variable name, so that we could ensure that the interstitial page that's currently loaded is actually the one that we're expecting (as opposed to maybe some earlier one that wasn't properly replaced for some reason)? Could that ever happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that's necessary. If there's already a launch.html?url tab open when Of course, if a completely different file is loaded at that URL, then we'll have a problem. While that's possible for the user to accomplish, that seems pathological and not worth defending against. |
||
} | ||
</script> | ||
<style type="text/css"> | ||
html, body { | ||
|
@@ -78,7 +70,7 @@ | |
} | ||
</style> | ||
</head> | ||
<body> | ||
<body onload="handleLoad()"> | ||
<div id="loading-image"> | ||
<div id="spinner"></div> | ||
</div> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: are are