Bug 1011031 - Itchpad: Reload file that has already been opened when changed on disk. r=harth

This commit is contained in:
Brian Grinstead 2014-06-21 09:30:00 -04:00
Родитель f116c9391a
Коммит a11d610e0a
10 изменённых файлов: 276 добавлений и 72 удалений

Просмотреть файл

@ -8,7 +8,7 @@ const ProjectEditor = require("projecteditor/projecteditor");
const SAMPLE_PATH = buildTempDirectoryStructure(); const SAMPLE_PATH = buildTempDirectoryStructure();
const SAMPLE_NAME = "DevTools Content Application Name"; const SAMPLE_NAME = "DevTools Content Application Name";
const SAMPLE_PROJECT_URL = "http://mozilla.org"; const SAMPLE_PROJECT_URL = "data:text/html;charset=utf-8,<body><h1>Project Overview</h1></body>";
const SAMPLE_ICON = "chrome://browser/skin/devtools/tool-debugger.svg"; const SAMPLE_ICON = "chrome://browser/skin/devtools/tool-debugger.svg";
/** /**
@ -16,40 +16,58 @@ const SAMPLE_ICON = "chrome://browser/skin/devtools/tool-debugger.svg";
* chrome://browser/content/devtools/projecteditor-loader.xul. * chrome://browser/content/devtools/projecteditor-loader.xul.
* This emulates the integration points that the app manager uses. * This emulates the integration points that the app manager uses.
*/ */
let appManagerEditor;
// Log a message to the project overview URL to make development easier
function log(msg) {
if (!appManagerEditor) {
return;
}
let doc = appManagerEditor.iframe.contentDocument;
let el = doc.createElement("p");
el.textContent = msg;
doc.body.appendChild(el);
}
document.addEventListener("DOMContentLoaded", function onDOMReady(e) { document.addEventListener("DOMContentLoaded", function onDOMReady(e) {
document.removeEventListener("DOMContentLoaded", onDOMReady, false); document.removeEventListener("DOMContentLoaded", onDOMReady, false);
let iframe = document.getElementById("projecteditor-iframe"); let iframe = document.getElementById("projecteditor-iframe");
window.projecteditor = ProjectEditor.ProjectEditor(iframe); window.projecteditor = ProjectEditor.ProjectEditor(iframe);
projecteditor.on("onEditorCreated", (editor) => { projecteditor.on("onEditorCreated", (editor, a) => {
console.log("editor created: " + editor); log("editor created: " + editor);
if (editor.label === "app-manager") {
appManagerEditor = editor;
appManagerEditor.on("load", function foo() {
appManagerEditor.off("load", foo);
log("Working on: " + SAMPLE_PATH);
})
}
}); });
projecteditor.on("onEditorDestroyed", (editor) => { projecteditor.on("onEditorDestroyed", (editor) => {
console.log("editor destroyed: " + editor); log("editor destroyed: " + editor);
}); });
projecteditor.on("onEditorSave", (editor, resource) => { projecteditor.on("onEditorSave", (editor, resource) => {
console.log("editor saved: " + editor, resource.path); log("editor saved: " + editor, resource.path);
}); });
projecteditor.on("onTreeSelected", (resource) => { projecteditor.on("onTreeSelected", (resource) => {
console.log("tree selected: " + resource.path); log("tree selected: " + resource.path);
}); });
projecteditor.on("onEditorLoad", (editor) => { projecteditor.on("onEditorLoad", (editor) => {
console.log("editor loaded: " + editor); log("editor loaded: " + editor);
}); });
projecteditor.on("onEditorActivated", (editor) => { projecteditor.on("onEditorActivated", (editor) => {
console.log("editor focused: " + editor); log("editor focused: " + editor);
}); });
projecteditor.on("onEditorDeactivated", (editor) => { projecteditor.on("onEditorDeactivated", (editor) => {
console.log("editor blur: " + editor); log("editor blur: " + editor);
}); });
projecteditor.on("onEditorChange", (editor) => { projecteditor.on("onEditorChange", (editor) => {
console.log("editor changed: " + editor); log("editor changed: " + editor);
});
projecteditor.on("onEditorCursorActivity", (editor) => {
console.log("editor cursor activity: " + editor);
}); });
projecteditor.on("onCommand", (cmd) => { projecteditor.on("onCommand", (cmd) => {
console.log("Command: " + cmd); log("Command: " + cmd);
}); });
projecteditor.loaded.then(() => { projecteditor.loaded.then(() => {

Просмотреть файл

@ -27,15 +27,27 @@ var AppProjectEditor = Class({
}, },
load: function(resource) { load: function(resource) {
this.elt.textContent = "";
let {appManagerOpts} = this.host.project; let {appManagerOpts} = this.host.project;
let iframe = this.iframe = this.elt.ownerDocument.createElement("iframe");
iframe.setAttribute("flex", "1");
iframe.setAttribute("src", appManagerOpts.projectOverviewURL);
this.elt.appendChild(iframe);
// Wait for other `appended` listeners before emitting load. // Only load the frame the first time it is selected
this.appended.then(() => { if (!this.iframe || this.iframe.getAttribute("src") !== appManagerOpts.projectOverviewURL) {
this.elt.textContent = "";
let iframe = this.iframe = this.elt.ownerDocument.createElement("iframe");
let iframeLoaded = this.iframeLoaded = promise.defer();
iframe.addEventListener("load", function onLoad() {
iframe.removeEventListener("load", onLoad);
iframeLoaded.resolve();
});
iframe.setAttribute("flex", "1");
iframe.setAttribute("src", appManagerOpts.projectOverviewURL);
this.elt.appendChild(iframe);
}
promise.all([this.iframeLoaded.promise, this.appended]).then(() => {
this.emit("load"); this.emit("load");
}); });
} }

Просмотреть файл

@ -19,7 +19,8 @@ var ImageEditor = Class({
}, },
load: function(resource) { load: function(resource) {
let image = this.doc.createElement("image"); this.elt.innerHTML = "";
let image = this.image = this.doc.createElement("image");
image.className = "editor-image"; image.className = "editor-image";
image.setAttribute("src", resource.uri); image.setAttribute("src", resource.uri);
@ -35,7 +36,15 @@ var ImageEditor = Class({
this.appended.then(() => { this.appended.then(() => {
this.emit("load"); this.emit("load");
}); });
},
destroy: function() {
if (this.image) {
this.image.remove();
this.image = null;
}
} }
}); });
exports.ImageEditor = ImageEditor; exports.ImageEditor = ImageEditor;

Просмотреть файл

@ -40,13 +40,9 @@ var Shell = Class({
this.editor.shell = this; this.editor.shell = this;
this.editorAppended = this.editor.appended; this.editorAppended = this.editor.appended;
let loadDefer = promise.defer();
this.editor.on("load", () => { this.editor.on("load", () => {
loadDefer.resolve(); this.editorDeferred.resolve();
}); });
this.editorLoaded = loadDefer.promise;
this.elt.appendChild(this.editor.elt); this.elt.appendChild(this.editor.elt);
}, },
@ -56,6 +52,8 @@ var Shell = Class({
* need to be added before calling this. * need to be added before calling this.
*/ */
load: function() { load: function() {
this.editorDeferred = promise.defer();
this.editorLoaded = this.editorDeferred.promise;
this.editor.load(this.resource); this.editor.load(this.resource);
}, },
@ -193,6 +191,8 @@ var ShellDeck = Class({
} }
this.deck.selectedPanel = shell.elt; this.deck.selectedPanel = shell.elt;
this._activeShell = shell; this._activeShell = shell;
shell.load();
shell.editorLoaded.then(() => { shell.editorLoaded.then(() => {
// Handle case where another shell has been requested before this // Handle case where another shell has been requested before this
// one is finished loading. // one is finished loading.

Просмотреть файл

@ -3,10 +3,13 @@ subsuite = devtools
support-files = support-files =
head.js head.js
helper_homepage.html helper_homepage.html
helper_edits.js
[browser_projecteditor_app_options.js] [browser_projecteditor_app_options.js]
[browser_projecteditor_delete_file.js] [browser_projecteditor_delete_file.js]
[browser_projecteditor_editing_01.js] [browser_projecteditor_editing_01.js]
[browser_projecteditor_editors_image.js]
[browser_projecteditor_external_change.js]
[browser_projecteditor_immediate_destroy.js] [browser_projecteditor_immediate_destroy.js]
[browser_projecteditor_init.js] [browser_projecteditor_init.js]
[browser_projecteditor_new_file.js] [browser_projecteditor_new_file.js]

Просмотреть файл

@ -4,6 +4,8 @@
"use strict"; "use strict";
loadHelperScript("helper_edits.js");
// Test ProjectEditor basic functionality // Test ProjectEditor basic functionality
let test = asyncTest(function*() { let test = asyncTest(function*() {
let projecteditor = yield addProjectEditorTabForTempDirectory(); let projecteditor = yield addProjectEditorTabForTempDirectory();
@ -14,49 +16,13 @@ let test = asyncTest(function*() {
ok (projecteditor.currentEditor, "There is an editor for projecteditor"); ok (projecteditor.currentEditor, "There is an editor for projecteditor");
let resources = projecteditor.project.allResources(); let resources = projecteditor.project.allResources();
resources.forEach((r, i) => { for (let data of helperEditData) {
console.log("Resource detected", r.path, i); info ("Processing " + data.path);
}); let resource = resources.filter(r=>r.basename === data.basename)[0];
yield selectFile(projecteditor, resource);
let stylesCss = resources.filter(r=>r.basename === "styles.css")[0]; yield testEditFile(projecteditor, getTempFile(data.path).path, data.newContent);
yield selectFile(projecteditor, stylesCss);
yield testEditFile(projecteditor, getTempFile("css/styles.css").path, "body,html { color: orange; }");
let indexHtml = resources.filter(r=>r.basename === "index.html")[0];
yield selectFile(projecteditor, indexHtml);
yield testEditFile(projecteditor, getTempFile("index.html").path, "<h1>Changed Content Again</h1>");
let license = resources.filter(r=>r.basename === "LICENSE")[0];
yield selectFile(projecteditor, license);
yield testEditFile(projecteditor, getTempFile("LICENSE").path, "My new license");
let readmeMd = resources.filter(r=>r.basename === "README.md")[0];
yield selectFile(projecteditor, readmeMd);
yield testEditFile(projecteditor, getTempFile("README.md").path, "My new license");
let scriptJs = resources.filter(r=>r.basename === "script.js")[0];
yield selectFile(projecteditor, scriptJs);
yield testEditFile(projecteditor, getTempFile("js/script.js").path, "alert('hi')");
let vectorSvg = resources.filter(r=>r.basename === "vector.svg")[0];
yield selectFile(projecteditor, vectorSvg);
yield testEditFile(projecteditor, getTempFile("img/icons/vector.svg").path, "<svg></svg>");
});
function selectFile (projecteditor, resource) {
ok (resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
projecteditor.projectTree.selectResource(resource);
if (resource.isDir) {
return;
} }
});
let [editorActivated] = yield promise.all([
onceEditorActivated(projecteditor)
]);
is (editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
}
function testEditFile(projecteditor, filePath, newData) { function testEditFile(projecteditor, filePath, newData) {
info ("Testing file editing for: " + filePath); info ("Testing file editing for: " + filePath);

Просмотреть файл

@ -0,0 +1,68 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
loadHelperScript("helper_edits.js");
// Test ProjectEditor image editor functionality
let test = asyncTest(function*() {
let projecteditor = yield addProjectEditorTabForTempDirectory();
let TEMP_PATH = projecteditor.project.allPaths()[0];
is (getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
ok (projecteditor.currentEditor, "There is an editor for projecteditor");
let resources = projecteditor.project.allResources();
let helperImageData = [
{
basename: "16x16.png",
path: "img/icons/16x16.png"
},
{
basename: "32x32.png",
path: "img/icons/32x32.png"
},
{
basename: "128x128.png",
path: "img/icons/128x128.png"
},
];
for (let data of helperImageData) {
info ("Processing " + data.path);
let resource = resources.filter(r=>r.basename === data.basename)[0];
yield selectFile(projecteditor, resource);
yield testEditor(projecteditor, getTempFile(data.path).path);
}
});
function testEditor(projecteditor, filePath) {
info ("Testing file editing for: " + filePath);
let editor = projecteditor.currentEditor;
let resource = projecteditor.resourceFor(editor);
is (resource.path, filePath, "Resource path is set correctly");
let images = editor.elt.querySelectorAll("image");
is (images.length, 1, "There is one image inside the editor");
is (images[0], editor.image, "The image property is set correctly with the DOM");
is (editor.image.getAttribute("src"), resource.uri, "The image has the resource URL");
info ("Selecting another resource, then reselecting this one");
projecteditor.projectTree.selectResource(resource.store.root);
yield onceEditorActivated(projecteditor);
projecteditor.projectTree.selectResource(resource);
yield onceEditorActivated(projecteditor);
let editor = projecteditor.currentEditor;
let images = editor.elt.querySelectorAll("image");
ok (images.length, 1, "There is one image inside the editor");
is (images[0], editor.image, "The image property is set correctly with the DOM");
is (editor.image.getAttribute("src"), resource.uri, "The image has the resource URL");
info ("Finished checking saving for " + filePath);
}

Просмотреть файл

@ -0,0 +1,52 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
loadHelperScript("helper_edits.js");
// Test ProjectEditor reaction to external changes (made outside of the)
// editor.
let test = asyncTest(function*() {
let projecteditor = yield addProjectEditorTabForTempDirectory();
let TEMP_PATH = projecteditor.project.allPaths()[0];
is (getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
ok (projecteditor.currentEditor, "There is an editor for projecteditor");
let resources = projecteditor.project.allResources();
for (let data of helperEditData) {
info ("Processing " + data.path);
let resource = resources.filter(r=>r.basename === data.basename)[0];
yield selectFile(projecteditor, resource);
yield testChangeFileExternally(projecteditor, getTempFile(data.path).path, data.newContent);
}
});
function testChangeFileExternally(projecteditor, filePath, newData) {
info ("Testing file external changes for: " + filePath);
let editor = projecteditor.currentEditor;
let resource = projecteditor.resourceFor(editor);
let initialData = yield getFileData(filePath);
is (resource.path, filePath, "Resource path is set correctly");
is (editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
info ("Editor has been selected, writing to file externally");
yield writeToFile(resource.path, newData);
info ("Selecting another resource, then reselecting this one");
projecteditor.projectTree.selectResource(resource.store.root);
yield onceEditorActivated(projecteditor);
projecteditor.projectTree.selectResource(resource);
yield onceEditorActivated(projecteditor);
let editor = projecteditor.currentEditor;
info ("Checking to make sure the editor is now populated correctly");
is (editor.editor.getText(), newData, "Editor has been updated with correct file contents");
info ("Finished checking saving for " + filePath);
}

Просмотреть файл

@ -72,6 +72,22 @@ function addTab(url) {
return def.promise; return def.promise;
} }
/**
* Some tests may need to import one or more of the test helper scripts.
* A test helper script is simply a js file that contains common test code that
* is either not common-enough to be in head.js, or that is located in a separate
* directory.
* The script will be loaded synchronously and in the test's scope.
* @param {String} filePath The file path, relative to the current directory.
* Examples:
* - "helper_attributes_test_runner.js"
* - "../../../commandline/test/helpers.js"
*/
function loadHelperScript(filePath) {
let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
Services.scriptloader.loadSubScript(testDir + "/" + filePath, this);
}
function addProjectEditorTabForTempDirectory() { function addProjectEditorTabForTempDirectory() {
TEMP_PATH = buildTempDirectoryStructure(); TEMP_PATH = buildTempDirectoryStructure();
let CUSTOM_OPTS = { let CUSTOM_OPTS = {
@ -173,7 +189,10 @@ function buildTempDirectoryStructure() {
// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file
function writeToFile(file, data) { function writeToFile(file, data) {
console.log("Writing to file: " + file.path, file.exists()); if (typeof file === "string") {
file = new FileUtils.File(file);
}
info("Writing to file: " + file.path + " (exists? " + file.exists() + ")");
let defer = promise.defer(); let defer = promise.defer();
var ostream = FileUtils.openSafeFileOutputStream(file); var ostream = FileUtils.openSafeFileOutputStream(file);
@ -188,7 +207,9 @@ function writeToFile(file, data) {
// Handle error! // Handle error!
info("ERROR WRITING TEMP FILE", status); info("ERROR WRITING TEMP FILE", status);
} }
defer.resolve();
}); });
return defer.promise;
} }
// This is used when setting up the test. // This is used when setting up the test.
@ -221,8 +242,10 @@ function getTempFile(path) {
} }
// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file
function* getFileData(path) { function* getFileData(file) {
let file = new FileUtils.File(path); if (typeof file === "string") {
file = new FileUtils.File(file);
}
let def = promise.defer(); let def = promise.defer();
NetUtil.asyncFetch(file, function(inputStream, status) { NetUtil.asyncFetch(file, function(inputStream, status) {

Просмотреть файл

@ -0,0 +1,53 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let helperEditData = [
{
basename: "styles.css",
path: "css/styles.css",
newContent: "body,html { color: orange; }"
},
{
basename: "index.html",
path: "index.html",
newContent: "<h1>Changed Content Again</h1>"
},
{
basename: "LICENSE",
path: "LICENSE",
newContent: "My new license"
},
{
basename: "README.md",
path: "README.md",
newContent: "My awesome readme"
},
{
basename: "script.js",
path: "js/script.js",
newContent: "alert('hi')"
},
{
basename: "vector.svg",
path: "img/icons/vector.svg",
newContent: "<svg></svg>"
},
];
function selectFile (projecteditor, resource) {
ok (resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
projecteditor.projectTree.selectResource(resource);
if (resource.isDir) {
return;
}
let [editorActivated] = yield promise.all([
onceEditorActivated(projecteditor)
]);
is (editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
}