1 | /*
|
2 | * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
|
3 | *
|
4 | * Permission is hereby granted, free of charge, to any person obtaining a
|
5 | * copy of this software and associated documentation files (the "Software"),
|
6 | * to deal in the Software without restriction, including without limitation
|
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
8 | * and/or sell copies of the Software, and to permit persons to whom the
|
9 | * Software is furnished to do so, subject to the following conditions:
|
10 | *
|
11 | * The above copyright notice and this permission notice shall be included in
|
12 | * all copies or substantial portions of the Software.
|
13 | *
|
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
20 | * DEALINGS IN THE SOFTWARE.
|
21 | *
|
22 | */
|
23 |
|
24 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
|
25 | /*global define, $, FileError, brackets, window */
|
26 |
|
27 | /**
|
28 | * LiveDevelopment manages the Inspector, all Agents, and the active LiveDocument
|
29 | *
|
30 | * # STARTING
|
31 | *
|
32 | * To start a session call `open`. This will read the currentDocument from brackets,
|
33 | * launch the LiveBrowser (currently Chrome) with the remote debugger port open,
|
34 | * establish the Inspector connection to the remote debugger, and finally load all
|
35 | * agents.
|
36 | *
|
37 | * # STOPPING
|
38 | *
|
39 | * To stop a session call `close`. This will close the active browser window,
|
40 | * disconnect the Inspector, unload all agents, and clean up.
|
41 | *
|
42 | * # STATUS
|
43 | *
|
44 | * Status updates are dispatched as `statusChange` jQuery events. The status
|
45 | * codes are:
|
46 | *
|
47 | * -1: Error
|
48 | * 0: Inactive
|
49 | * 1: Connecting to the remote debugger
|
50 | * 2: Loading agents
|
51 | * 3: Active
|
52 | * 4: Out of sync
|
53 | */
|
54 | define(function LiveDevelopment(require, exports, module) {
|
55 | ;
|
56 |
|
57 | require("utils/Global");
|
58 |
|
59 | // Status Codes
|
60 | var STATUS_ERROR = exports.STATUS_ERROR = -1;
|
61 | var STATUS_INACTIVE = exports.STATUS_INACTIVE = 0;
|
62 | var STATUS_CONNECTING = exports.STATUS_CONNECTING = 1;
|
63 | var STATUS_LOADING_AGENTS = exports.STATUS_LOADING_AGENTS = 2;
|
64 | var STATUS_ACTIVE = exports.STATUS_ACTIVE = 3;
|
65 | var STATUS_OUT_OF_SYNC = exports.STATUS_OUT_OF_SYNC = 4;
|
66 |
|
67 | var Dialogs = require("widgets/Dialogs"),
|
68 | DocumentManager = require("document/DocumentManager"),
|
69 | EditorManager = require("editor/EditorManager"),
|
70 | FileUtils = require("file/FileUtils"),
|
71 | NativeApp = require("utils/NativeApp"),
|
72 | PreferencesDialogs = require("preferences/PreferencesDialogs"),
|
73 | ProjectManager = require("project/ProjectManager"),
|
74 | Strings = require("strings"),
|
75 | StringUtils = require("utils/StringUtils");
|
76 |
|
77 | // Inspector
|
78 | var Inspector = require("LiveDevelopment/Inspector/Inspector");
|
79 |
|
80 | // Documents
|
81 | var CSSDocument = require("LiveDevelopment/Documents/CSSDocument"),
|
82 | HTMLDocument = require("LiveDevelopment/Documents/HTMLDocument"),
|
83 | JSDocument = require("LiveDevelopment/Documents/JSDocument");
|
84 |
|
85 | // Agents
|
86 | var agents = {
|
87 | "console" : require("LiveDevelopment/Agents/ConsoleAgent"),
|
88 | "remote" : require("LiveDevelopment/Agents/RemoteAgent"),
|
89 | "network" : require("LiveDevelopment/Agents/NetworkAgent"),
|
90 | "dom" : require("LiveDevelopment/Agents/DOMAgent"),
|
91 | "css" : require("LiveDevelopment/Agents/CSSAgent"),
|
92 | "script" : require("LiveDevelopment/Agents/ScriptAgent"),
|
93 | "highlight" : require("LiveDevelopment/Agents/HighlightAgent"),
|
94 | "goto" : require("LiveDevelopment/Agents/GotoAgent"),
|
95 | "edit" : require("LiveDevelopment/Agents/EditAgent")
|
96 | };
|
97 |
|
98 | // Some agents are still experimental, so we don't enable them all by default
|
99 | // However, extensions can enable them by calling enableAgent().
|
100 | // This object is used as a set (thus all properties have the value 'true').
|
101 | // Property names should match property names in the 'agents' object.
|
102 | var _enabledAgentNames = {
|
103 | "console" : true,
|
104 | "remote" : true,
|
105 | "network" : true,
|
106 | "dom" : true,
|
107 | "css" : true
|
108 | };
|
109 |
|
110 | // store the names (matching property names in the 'agent' object) of agents that we've loaded
|
111 | var _loadedAgentNames = [];
|
112 |
|
113 | var _liveDocument; // the document open for live editing.
|
114 | var _relatedDocuments; // CSS and JS documents that are used by the live HTML document
|
115 |
|
116 | function _isHtmlFileExt(ext) {
|
117 | return (FileUtils.isStaticHtmlFileExt(ext) ||
|
118 | (ProjectManager.getBaseUrl() && FileUtils.isServerHtmlFileExt(ext)));
|
119 | }
|
120 |
|
121 | /** Convert a URL to a local full file path */
|
122 | function _urlToPath(url) {
|
123 | var path,
|
124 | baseUrl = ProjectManager.getBaseUrl();
|
125 |
|
126 | if (baseUrl !== "" && url.indexOf(baseUrl) === 0) {
|
127 | // Use base url to translate to local file path.
|
128 | // Need to use encoded project path because it's decoded below.
|
129 | path = url.replace(baseUrl, encodeURI(ProjectManager.getProjectRoot().fullPath));
|
130 |
|
131 | } else if (url.indexOf("file://") === 0) {
|
132 | // Convert a file URL to local file path
|
133 | path = url.slice(7);
|
134 | if (path && brackets.platform === "win" && path.charAt(0) === "/") {
|
135 | path = path.slice(1);
|
136 | }
|
137 | }
|
138 | return decodeURI(path);
|
139 | }
|
140 |
|
141 | /** Convert a local full file path to a URL */
|
142 | function _pathToUrl(path) {
|
143 | var url,
|
144 | baseUrl = ProjectManager.getBaseUrl();
|
145 |
|
146 | // See if base url has been specified and path is within project
|
147 | if (baseUrl !== "" && ProjectManager.isWithinProject(path)) {
|
148 | // Map to server url. Base url is already encoded, so don't encode again.
|
149 | var encodedDocPath = encodeURI(path);
|
150 | var encodedProjectPath = encodeURI(ProjectManager.getProjectRoot().fullPath);
|
151 | url = encodedDocPath.replace(encodedProjectPath, baseUrl);
|
152 |
|
153 | } else {
|
154 | var prefix = "file://";
|
155 |
|
156 | if (brackets.platform === "win") {
|
157 | // The path on Windows starts with a drive letter (e.g. "C:").
|
158 | // In order to make it a valid file: URL we need to add an
|
159 | // additional slash to the prefix.
|
160 | prefix += "/";
|
161 | }
|
162 |
|
163 | url = encodeURI(prefix + path);
|
164 | }
|
165 |
|
166 | return url;
|
167 | }
|
168 |
|
169 | /** Augments the given Brackets document with information that's useful for live development. */
|
170 | function _setDocInfo(doc) {
|
171 |
|
172 | var parentUrl,
|
173 | rootUrl,
|
174 | matches;
|
175 |
|
176 | // FUTURE: some of these things should just be moved into core Document; others should
|
177 | // be in a LiveDevelopment-specific object attached to the doc.
|
178 | matches = /^(.*\/)(.+\.([^.]+))$/.exec(doc.file.fullPath);
|
179 | if (!matches) {
|
180 | return;
|
181 | }
|
182 |
|
183 | doc.extension = matches[3];
|
184 |
|
185 | parentUrl = _pathToUrl(matches[1]);
|
186 | doc.url = parentUrl + encodeURI(matches[2]);
|
187 |
|
188 | // the root represents the document that should be displayed in the browser
|
189 | // for live development (the file for HTML files, index.html for others)
|
190 | // TODO: Issue #2033 Improve how default page is determined
|
191 | rootUrl = (_isHtmlFileExt(matches[3]) ? doc.url : parentUrl + "index.html");
|
192 | doc.root = { url: rootUrl };
|
193 | }
|
194 |
|
195 | /** Get the current document from the document manager
|
196 | * _adds extension, url and root to the document
|
197 | */
|
198 | function _getCurrentDocument() {
|
199 | var doc = DocumentManager.getCurrentDocument();
|
200 | if (doc) {
|
201 | _setDocInfo(doc);
|
202 | }
|
203 | return doc;
|
204 | }
|
205 |
|
206 | /** Determine which document class should be used for a given document
|
207 | * @param {Document} document
|
208 | */
|
209 | function _classForDocument(doc) {
|
210 | switch (doc.extension) {
|
211 | case "css":
|
212 | return CSSDocument;
|
213 | case "js":
|
214 | return exports.config.experimental ? JSDocument : null;
|
215 | }
|
216 |
|
217 | if (exports.config.experimental && _isHtmlFileExt(doc.extension)) {
|
218 | return HTMLDocument;
|
219 | }
|
220 |
|
221 | return null;
|
222 | }
|
223 |
|
224 | /**
|
225 | * Removes the given CSS/JSDocument from _relatedDocuments. Signals that the
|
226 | * given file is no longer associated with the HTML document that is live (e.g.
|
227 | * if the related file has been deleted on disk).
|
228 | */
|
229 | function _handleRelatedDocumentDeleted(event, liveDoc) {
|
230 | var index = _relatedDocuments.indexOf(liveDoc);
|
231 | if (index !== -1) {
|
232 | $(liveDoc).on("deleted", _handleRelatedDocumentDeleted);
|
233 | _relatedDocuments.splice(index, 1);
|
234 | }
|
235 | }
|
236 |
|
237 | /** Close a live document */
|
238 | function _closeDocument() {
|
239 | if (_liveDocument) {
|
240 | _liveDocument.close();
|
241 | _liveDocument = undefined;
|
242 | }
|
243 | if (_relatedDocuments) {
|
244 | _relatedDocuments.forEach(function (liveDoc) {
|
245 | liveDoc.close();
|
246 | $(liveDoc).off("deleted", _handleRelatedDocumentDeleted);
|
247 | });
|
248 | _relatedDocuments = undefined;
|
249 | }
|
250 | }
|
251 |
|
252 | /** Create a live version of a Brackets document */
|
253 | function _createDocument(doc, editor) {
|
254 | var DocClass = _classForDocument(doc);
|
255 | if (DocClass) {
|
256 | return new DocClass(doc, editor);
|
257 | } else {
|
258 | return null;
|
259 | }
|
260 | }
|
261 |
|
262 | /** Open a live document
|
263 | * @param {Document} source document to open
|
264 | */
|
265 | function _openDocument(doc, editor) {
|
266 | _closeDocument();
|
267 | _liveDocument = _createDocument(doc, editor);
|
268 |
|
269 | // Gather related CSS documents.
|
270 | // FUTURE: Gather related JS documents as well.
|
271 | _relatedDocuments = [];
|
272 | agents.css.getStylesheetURLs().forEach(function (url) {
|
273 | // FUTURE: when we get truly async file handling, we might need to prevent other
|
274 | // stuff from happening while we wait to add these listeners
|
275 | DocumentManager.getDocumentForPath(_urlToPath(url))
|
276 | .done(function (doc) {
|
277 | _setDocInfo(doc);
|
278 | var liveDoc = _createDocument(doc);
|
279 | if (liveDoc) {
|
280 | _relatedDocuments.push(liveDoc);
|
281 | $(liveDoc).on("deleted", _handleRelatedDocumentDeleted);
|
282 | }
|
283 | });
|
284 | });
|
285 | }
|
286 |
|
287 | /** Unload the agents */
|
288 | function unloadAgents() {
|
289 | _loadedAgentNames.forEach(function (name) {
|
290 | agents[name].unload();
|
291 | });
|
292 | _loadedAgentNames = [];
|
293 | }
|
294 |
|
295 | /** Load the agents */
|
296 | function loadAgents() {
|
297 | var name, promises = [];
|
298 | var agentsToLoad;
|
299 | if (exports.config.experimental) {
|
300 | // load all agents
|
301 | agentsToLoad = agents;
|
302 | } else {
|
303 | // load only enabled agents
|
304 | agentsToLoad = _enabledAgentNames;
|
305 | }
|
306 | for (name in agentsToLoad) {
|
307 | if (agentsToLoad.hasOwnProperty(name) && agents[name] && agents[name].load) {
|
308 | promises.push(agents[name].load());
|
309 | _loadedAgentNames.push(name);
|
310 | }
|
311 | }
|
312 | return promises;
|
313 | }
|
314 |
|
315 | /** Enable an agent. Takes effect next time a connection is made. Does not affect
|
316 | * current live development sessions.
|
317 | *
|
318 | * @param {string} name of agent to enable
|
319 | */
|
320 | function enableAgent(name) {
|
321 | if (agents.hasOwnProperty(name) && !_enabledAgentNames.hasOwnProperty(name)) {
|
322 | _enabledAgentNames[name] = true;
|
323 | }
|
324 | }
|
325 |
|
326 | /** Disable an agent. Takes effect next time a connection is made. Does not affect
|
327 | * current live development sessions.
|
328 | *
|
329 | * @param {string} name of agent to disable
|
330 | */
|
331 | function disableAgent(name) {
|
332 | if (_enabledAgentNames.hasOwnProperty(name)) {
|
333 | delete _enabledAgentNames[name];
|
334 | }
|
335 | }
|
336 |
|
337 | /** Update the status
|
338 | * @param {integer} new status
|
339 | */
|
340 | function _setStatus(status) {
|
341 | exports.status = status;
|
342 | $(exports).triggerHandler("statusChange", status);
|
343 | }
|
344 |
|
345 | /** Triggered by Inspector.error */
|
346 | function _onError(event, error) {
|
347 | var message;
|
348 |
|
349 | // Sometimes error.message is undefined
|
350 | if (!error.message) {
|
351 | console.warn("Expected a non-empty string in error.message, got this instead:", error.message);
|
352 | message = JSON.stringify(error);
|
353 | } else {
|
354 | message = error.message;
|
355 | }
|
356 |
|
357 | // Remove "Uncaught" from the beginning to avoid the inspector popping up
|
358 | if (message && message.substr(0, 8) === "Uncaught") {
|
359 | message = message.substr(9);
|
360 | }
|
361 |
|
362 | // Additional information, like exactly which parameter could not be processed.
|
363 | var data = error.data;
|
364 | if (Array.isArray(data)) {
|
365 | message += "\n" + data.join("\n");
|
366 | }
|
367 |
|
368 | // Show the message, but include the error object for further information (e.g. error code)
|
369 | console.error(message, error);
|
370 | _setStatus(STATUS_ERROR);
|
371 | }
|
372 |
|
373 | /** Run when all agents are loaded */
|
374 | function _onLoad() {
|
375 | var doc = _getCurrentDocument();
|
376 | if (doc) {
|
377 | var editor = EditorManager.getCurrentFullEditor(),
|
378 | status = STATUS_ACTIVE;
|
379 |
|
380 | _openDocument(doc, editor);
|
381 | if (doc.isDirty && _classForDocument(doc) !== CSSDocument) {
|
382 | status = STATUS_OUT_OF_SYNC;
|
383 | }
|
384 | _setStatus(status);
|
385 | }
|
386 | }
|
387 |
|
388 | /** Triggered by Inspector.detached */
|
389 | function _onDetached(event, res) {
|
390 | // res.reason, e.g. "replaced_with_devtools", "target_closed", "canceled_by_user"
|
391 | // Sample list taken from https://chromiumcodereview.appspot.com/10947037/patch/12001/13004
|
392 | // However, the link refers to the Chrome Extension API, it may not apply 100% to the Inspector API
|
393 | }
|
394 |
|
395 | /** Triggered by Inspector.connect */
|
396 | function _onConnect(event) {
|
397 | $(Inspector.Inspector).on("detached", _onDetached);
|
398 | var promises = loadAgents();
|
399 | _setStatus(STATUS_LOADING_AGENTS);
|
400 | $.when.apply(undefined, promises).then(_onLoad, _onError);
|
401 | }
|
402 |
|
403 | /** Triggered by Inspector.disconnect */
|
404 | function _onDisconnect(event) {
|
405 | $(Inspector.Inspector).off("detached", _onDetached);
|
406 | unloadAgents();
|
407 | _closeDocument();
|
408 | _setStatus(STATUS_INACTIVE);
|
409 | }
|
410 |
|
411 | function _onReconnect() {
|
412 | unloadAgents();
|
413 | var promises = loadAgents();
|
414 | _setStatus(STATUS_LOADING_AGENTS);
|
415 | $.when.apply(undefined, promises).then(_onLoad, _onError);
|
416 | }
|
417 |
|
418 | /** Open the Connection and go live */
|
419 | function open() {
|
420 | var result = new $.Deferred(),
|
421 | promise = result.promise();
|
422 | var doc = _getCurrentDocument();
|
423 | var browserStarted = false;
|
424 | var retryCount = 0;
|
425 |
|
426 | function showWrongDocError() {
|
427 | Dialogs.showModalDialog(
|
428 | Dialogs.DIALOG_ID_ERROR,
|
429 | Strings.LIVE_DEVELOPMENT_ERROR_TITLE,
|
430 | Strings.LIVE_DEV_NEED_HTML_MESSAGE
|
431 | );
|
432 | result.reject();
|
433 | }
|
434 |
|
435 | function showNeedBaseUrlError() {
|
436 | PreferencesDialogs.showProjectPreferencesDialog("", Strings.LIVE_DEV_NEED_BASEURL_MESSAGE)
|
437 | .done(function (id) {
|
438 | if (id === Dialogs.DIALOG_BTN_OK && ProjectManager.getBaseUrl()) {
|
439 | // If base url is specifed, then re-invoke open() to continue
|
440 | open();
|
441 | result.resolve();
|
442 | } else {
|
443 | result.reject();
|
444 | }
|
445 | })
|
446 | .fail(function () {
|
447 | result.reject();
|
448 | });
|
449 | }
|
450 |
|
451 | if (!doc || !doc.root) {
|
452 | showWrongDocError();
|
453 |
|
454 | } else {
|
455 | if (!exports.config.experimental) {
|
456 | if (FileUtils.isServerHtmlFileExt(doc.extension)) {
|
457 | if (!ProjectManager.getBaseUrl()) {
|
458 | showNeedBaseUrlError();
|
459 | return promise;
|
460 | }
|
461 | } else if (!FileUtils.isStaticHtmlFileExt(doc.extension)) {
|
462 | showWrongDocError();
|
463 | return promise;
|
464 | }
|
465 | }
|
466 |
|
467 | _setStatus(STATUS_CONNECTING);
|
468 | Inspector.connectToURL(doc.root.url).then(result.resolve, function onConnectFail(err) {
|
469 | if (err === "CANCEL") {
|
470 | result.reject(err);
|
471 | return;
|
472 | }
|
473 | if (retryCount > 6) {
|
474 | _setStatus(STATUS_ERROR);
|
475 | Dialogs.showModalDialog(
|
476 | Dialogs.DIALOG_ID_LIVE_DEVELOPMENT,
|
477 | Strings.LIVE_DEVELOPMENT_RELAUNCH_TITLE,
|
478 | Strings.LIVE_DEVELOPMENT_ERROR_MESSAGE
|
479 | ).done(function (id) {
|
480 | if (id === Dialogs.DIALOG_BTN_OK) {
|
481 | // User has chosen to reload Chrome, quit the running instance
|
482 | _setStatus(STATUS_INACTIVE);
|
483 | NativeApp.closeLiveBrowser()
|
484 | .done(function () {
|
485 | browserStarted = false;
|
486 | window.setTimeout(function () {
|
487 | open().then(result.resolve, result.reject);
|
488 | });
|
489 | })
|
490 | .fail(function (err) {
|
491 | // Report error?
|
492 | _setStatus(STATUS_ERROR);
|
493 | browserStarted = false;
|
494 | result.reject("CLOSE_LIVE_BROWSER");
|
495 | });
|
496 | } else {
|
497 | result.reject("CANCEL");
|
498 | }
|
499 | });
|
500 | return;
|
501 | }
|
502 | retryCount++;
|
503 |
|
504 | if (!browserStarted && exports.status !== STATUS_ERROR) {
|
505 | // If err === FileError.ERR_NOT_FOUND, it means a remote debugger connection
|
506 | // is available, but the requested URL is not loaded in the browser. In that
|
507 | // case we want to launch the live browser (to open the url in a new tab)
|
508 | // without using the --remote-debugging-port flag. This works around issues
|
509 | // on Windows where Chrome can't be opened more than once with the
|
510 | // --remote-debugging-port flag set.
|
511 | NativeApp.openLiveBrowser(
|
512 | doc.root.url,
|
513 | err !== FileError.ERR_NOT_FOUND
|
514 | )
|
515 | .done(function () {
|
516 | browserStarted = true;
|
517 | })
|
518 | .fail(function (err) {
|
519 | var message;
|
520 |
|
521 | _setStatus(STATUS_ERROR);
|
522 | if (err === FileError.NOT_FOUND_ERR) {
|
523 | message = Strings.ERROR_CANT_FIND_CHROME;
|
524 | } else {
|
525 | message = StringUtils.format(Strings.ERROR_LAUNCHING_BROWSER, err);
|
526 | }
|
527 |
|
528 | // Append a message to direct users to the troubleshooting page.
|
529 | if (message) {
|
530 | message += " " + StringUtils.format(Strings.LIVE_DEVELOPMENT_TROUBLESHOOTING, brackets.config.troubleshoot_url);
|
531 | }
|
532 |
|
533 | Dialogs.showModalDialog(
|
534 | Dialogs.DIALOG_ID_ERROR,
|
535 | Strings.ERROR_LAUNCHING_BROWSER_TITLE,
|
536 | message
|
537 | );
|
538 |
|
539 | result.reject("OPEN_LIVE_BROWSER");
|
540 | });
|
541 | }
|
542 |
|
543 | if (exports.status !== STATUS_ERROR) {
|
544 | window.setTimeout(function retryConnect() {
|
545 | Inspector.connectToURL(doc.root.url).then(result.resolve, onConnectFail);
|
546 | }, 500);
|
547 | }
|
548 | });
|
549 | }
|
550 |
|
551 | return promise;
|
552 | }
|
553 |
|
554 | /** Close the Connection */
|
555 | function close() {
|
556 | if (Inspector.connected()) {
|
557 | Inspector.Runtime.evaluate("window.close()");
|
558 | }
|
559 | Inspector.disconnect();
|
560 | _setStatus(STATUS_INACTIVE);
|
561 | }
|
562 |
|
563 | /** Triggered by a document change from the DocumentManager */
|
564 | function _onDocumentChange() {
|
565 | var doc = _getCurrentDocument(),
|
566 | status = STATUS_ACTIVE;
|
567 | if (!doc) {
|
568 | return;
|
569 | }
|
570 |
|
571 | if (Inspector.connected()) {
|
572 | if (agents.network && agents.network.wasURLRequested(doc.url)) {
|
573 | _closeDocument();
|
574 | var editor = EditorManager.getCurrentFullEditor();
|
575 | _openDocument(doc, editor);
|
576 | } else {
|
577 | if (exports.config.experimental || _isHtmlFileExt(doc.extension)) {
|
578 | close();
|
579 | window.setTimeout(open);
|
580 | }
|
581 | }
|
582 |
|
583 | if (doc.isDirty && _classForDocument(doc) !== CSSDocument) {
|
584 | status = STATUS_OUT_OF_SYNC;
|
585 | }
|
586 | _setStatus(status);
|
587 | }
|
588 | }
|
589 |
|
590 | /** Triggered by a document saved from the DocumentManager */
|
591 | function _onDocumentSaved(event, doc) {
|
592 | if (doc && Inspector.connected() && _classForDocument(doc) !== CSSDocument &&
|
593 | agents.network && agents.network.wasURLRequested(doc.url)) {
|
594 | // Reload HTML page
|
595 | Inspector.Page.reload();
|
596 |
|
597 | // Reload unsaved changes
|
598 | _onReconnect();
|
599 | }
|
600 | }
|
601 |
|
602 | /** Triggered by a change in dirty flag from the DocumentManager */
|
603 | function _onDirtyFlagChange(event, doc) {
|
604 | if (doc && Inspector.connected() && _classForDocument(doc) !== CSSDocument &&
|
605 | agents.network && agents.network.wasURLRequested(doc.url)) {
|
606 | // Set status to out of sync if dirty. Otherwise, set it to active status.
|
607 | _setStatus(doc.isDirty ? STATUS_OUT_OF_SYNC : STATUS_ACTIVE);
|
608 | }
|
609 | }
|
610 |
|
611 | function getLiveDocForPath(path) {
|
612 | var docsToSearch = [];
|
613 | if (_relatedDocuments) {
|
614 | docsToSearch = docsToSearch.concat(_relatedDocuments);
|
615 | }
|
616 | if (_liveDocument) {
|
617 | docsToSearch = docsToSearch.concat(_liveDocument);
|
618 | }
|
619 | var foundDoc;
|
620 | docsToSearch.some(function matchesPath(ele) {
|
621 | if (ele.doc.file.fullPath === path) {
|
622 | foundDoc = ele;
|
623 | return true;
|
624 | }
|
625 | return false;
|
626 | });
|
627 |
|
628 | return foundDoc;
|
629 | }
|
630 |
|
631 | /** Hide any active highlighting */
|
632 | function hideHighlight() {
|
633 | if (Inspector.connected() && agents.highlight) {
|
634 | agents.highlight.hide();
|
635 | }
|
636 | }
|
637 |
|
638 | /** Initialize the LiveDevelopment Session */
|
639 | function init(theConfig) {
|
640 | exports.config = theConfig;
|
641 | $(Inspector).on("connect", _onConnect)
|
642 | .on("disconnect", _onDisconnect)
|
643 | .on("error", _onError);
|
644 | $(DocumentManager).on("currentDocumentChange", _onDocumentChange)
|
645 | .on("documentSaved", _onDocumentSaved)
|
646 | .on("dirtyFlagChange", _onDirtyFlagChange);
|
647 | }
|
648 |
|
649 | // For unit testing
|
650 | exports._pathToUrl = _pathToUrl;
|
651 | exports._urlToPath = _urlToPath;
|
652 |
|
653 | // Export public functions
|
654 | exports.agents = agents;
|
655 | exports.open = open;
|
656 | exports.close = close;
|
657 | exports.enableAgent = enableAgent;
|
658 | exports.disableAgent = disableAgent;
|
659 | exports.getLiveDocForPath = getLiveDocForPath;
|
660 | exports.hideHighlight = hideHighlight;
|
661 | exports.init = init;
|
662 | }); |
\ | No newline at end of file |