diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index bde98b9e87d..7566cd55ce5 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -156,7 +156,8 @@ var Scratchpad = { return { filename: this.filename, text: this.getText(), - executionContext: this.executionContext + executionContext: this.executionContext, + saved: this.saved }; }, @@ -172,6 +173,7 @@ var Scratchpad = { if (aState.filename) { this.setFilename(aState.filename); } + this.saved = aState.saved; if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER) { this.setBrowserContext(); @@ -591,7 +593,7 @@ var Scratchpad = { fp.defaultString = ""; if (fp.show() != Ci.nsIFilePicker.returnCancel) { this.setFilename(fp.file.path); - this.importFromFile(fp.file); + this.importFromFile(fp.file, false, this.onTextSaved.bind(this)); } }, @@ -606,7 +608,7 @@ var Scratchpad = { let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(this.filename); - this.exportToFile(file, true); + this.exportToFile(file, true, false, this.onTextSaved.bind(this)); }, /** @@ -619,8 +621,8 @@ var Scratchpad = { Ci.nsIFilePicker.modeSave); fp.defaultString = "scratchpad.js"; if (fp.show() != Ci.nsIFilePicker.returnCancel) { - document.title = this.filename = fp.file.path; - this.exportToFile(fp.file, true); + this.setFilename(fp.file.path); + this.exportToFile(fp.file, true, false, this.onTextSaved.bind(this)); } }, @@ -759,6 +761,13 @@ var Scratchpad = { this.onContextMenu); this.editor.focus(); this.editor.setCaretOffset(this.editor.getCharCount()); + + if (this.filename && !this.saved) { + this.onTextChanged(); + } + else if (this.filename && this.saved) { + this.onTextSaved(); + } }, /** @@ -823,6 +832,33 @@ var Scratchpad = { this.editor.redo(); }, + /** + * This method adds a listener to the editor for text changes. Called when + * a scratchpad is saved, opened from file, or restored from a saved file. + */ + onTextSaved: function SP_onTextSaved(aStatus) + { + if (aStatus && !Components.isSuccessCode(aStatus)) { + return; + } + document.title = document.title.replace(/^\*/, ""); + this.saved = true; + this.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, + this.onTextChanged); + }, + + /** + * The scratchpad handler for editor text change events. This handler + * indicates that there are unsaved changes in the UI. + */ + onTextChanged: function SP_onTextChanged() + { + document.title = "*" + document.title; + Scratchpad.saved = false; + Scratchpad.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, + Scratchpad.onTextChanged); + }, + /** * The Scratchpad window unload event handler. This method unloads/destroys * the source editor. diff --git a/browser/devtools/scratchpad/test/Makefile.in b/browser/devtools/scratchpad/test/Makefile.in index ea528144295..7938a3f666d 100644 --- a/browser/devtools/scratchpad/test/Makefile.in +++ b/browser/devtools/scratchpad/test/Makefile.in @@ -57,6 +57,7 @@ _BROWSER_TEST_FILES = \ browser_scratchpad_restore.js \ browser_scratchpad_bug_679467_falsy.js \ browser_scratchpad_bug_699130_edit_ui_updates.js \ + browser_scratchpad_bug_669612_unsaved.js \ libs:: $(_BROWSER_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js new file mode 100644 index 00000000000..1ab84a8171a --- /dev/null +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js @@ -0,0 +1,140 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// only finish() when correct number of tests are done +const expected = 5; +var count = 0; +function done() +{ + if (++count == expected) { + finish(); + } +} + +var ScratchpadManager = Scratchpad.ScratchpadManager; + + +function test() +{ + waitForExplicitFinish(); + + testListeners(); + testErrorStatus(); + testRestoreNotFromFile(); + testRestoreFromFileSaved(); + testRestoreFromFileUnsaved(); + + content.location = "data:text/html,
test star* UI for unsaved file changes"; +} + +function testListeners() +{ + let win = ScratchpadManager.openScratchpad(); + + win.addEventListener("load", function() { + let scratchpad = win.Scratchpad; + + scratchpad.setText("new text"); + ok(!isStar(win), "no star if scratchpad isn't from a file"); + + scratchpad.onTextSaved(); + ok(!isStar(win), "no star before changing text"); + + scratchpad.setText("new text2"); + ok(isStar(win), "shows star if scratchpad text changes"); + + scratchpad.onTextSaved(); + ok(!isStar(win), "no star if scratchpad was just saved"); + + scratchpad.undo(); + ok(isStar(win), "star if scratchpad undo"); + + win.close(); + done(); + }); +} + +function testErrorStatus() +{ + let win = ScratchpadManager.openScratchpad(); + + win.addEventListener("load", function() { + let scratchpad = win.Scratchpad; + + scratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE); + scratchpad.setText("new text"); + ok(!isStar(win), "no star if file save failed"); + + win.close(); + done(); + }); +} + + +function testRestoreNotFromFile() +{ + let session = [{ + text: "test1", + executionContext: 1 + }]; + + let [win] = ScratchpadManager.restoreSession(session); + win.addEventListener("load", function() { + let scratchpad = win.Scratchpad; + + scratchpad.setText("new text"); + ok(!isStar(win), "no star if restored scratchpad isn't from a file"); + + win.close(); + done(); + }); +} + +function testRestoreFromFileSaved() +{ + let session = [{ + filename: "test.js", + text: "test1", + executionContext: 1, + saved: true + }]; + + let [win] = ScratchpadManager.restoreSession(session); + win.addEventListener("load", function() { + let scratchpad = win.Scratchpad; + + ok(!isStar(win), "no star before changing text in scratchpad restored from file"); + + scratchpad.setText("new text"); + ok(isStar(win), "star when text changed from scratchpad restored from file"); + + win.close(); + done(); + }); +} + +function testRestoreFromFileUnsaved() +{ + let session = [{ + filename: "test.js", + text: "test1", + executionContext: 1, + saved: false + }]; + + let [win] = ScratchpadManager.restoreSession(session); + win.addEventListener("load", function() { + let scratchpad = win.Scratchpad; + + ok(isStar(win), "star with scratchpad restored with unsaved text"); + + win.close(); + done(); + }); +} + +function isStar(win) +{ + return win.document.title.match(/^\*[^\*]/); +}