mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 01:48:05 +00:00
Bug 826982 - [style editor] Show a notification when navigating away from page if there are unsaved changes; r=paul
This commit is contained in:
parent
5a0713f2f8
commit
2d1c745b79
@ -177,6 +177,28 @@ StyleEditorChrome.prototype = {
|
||||
return editors;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get whether any of the editors have unsaved changes.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
get isDirty()
|
||||
{
|
||||
if (this._markedDirty === true) {
|
||||
return true;
|
||||
}
|
||||
return this.editors.some(function(editor) {
|
||||
return editor.sourceEditor && editor.sourceEditor.dirty;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Mark the style editor as having unsaved changes.
|
||||
*/
|
||||
markDirty: function SEC_markDirty() {
|
||||
this._markedDirty = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a listener for StyleEditorChrome events.
|
||||
*
|
||||
|
@ -4,11 +4,12 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["StyleEditorPanel"];
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
||||
|
||||
@ -21,11 +22,11 @@ this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
|
||||
this._toolbox = toolbox;
|
||||
this._target = toolbox.target;
|
||||
|
||||
this.reset = this.reset.bind(this);
|
||||
this.newPage = this.newPage.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.beforeNavigate = this.beforeNavigate.bind(this);
|
||||
|
||||
this._target.on("will-navigate", this.reset);
|
||||
this._target.on("will-navigate", this.beforeNavigate);
|
||||
this._target.on("navigate", this.newPage);
|
||||
this._target.on("close", this.destroy);
|
||||
|
||||
@ -86,9 +87,87 @@ StyleEditorPanel.prototype = {
|
||||
* Navigated to a new page.
|
||||
*/
|
||||
newPage: function StyleEditor_newPage(event, window) {
|
||||
this.reset();
|
||||
this.setPage(window);
|
||||
},
|
||||
|
||||
/**
|
||||
* Before navigating to a new page or reloading the page.
|
||||
*/
|
||||
beforeNavigate: function StyleEditor_beforeNavigate(event, request) {
|
||||
if (this.styleEditorChrome.isDirty) {
|
||||
this.preventNavigate(request);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a notificiation about losing unsaved changes.
|
||||
*/
|
||||
preventNavigate: function StyleEditor_preventNavigate(request) {
|
||||
request.suspend();
|
||||
|
||||
let notificationBox = null;
|
||||
if (this.target.isLocalTab) {
|
||||
let gBrowser = this.target.tab.ownerDocument.defaultView.gBrowser;
|
||||
notificationBox = gBrowser.getNotificationBox();
|
||||
}
|
||||
else {
|
||||
notificationBox = this._toolbox.getNotificationBox();
|
||||
}
|
||||
|
||||
let notification = notificationBox.
|
||||
getNotificationWithValue("styleeditor-page-navigation");
|
||||
|
||||
if (notification) {
|
||||
notificationBox.removeNotification(notification, true);
|
||||
}
|
||||
|
||||
let cancelRequest = function onCancelRequest() {
|
||||
if (request) {
|
||||
request.cancel(Cr.NS_BINDING_ABORTED);
|
||||
request.resume(); // needed to allow the connection to be cancelled.
|
||||
request = null;
|
||||
}
|
||||
};
|
||||
|
||||
let eventCallback = function onNotificationCallback(event) {
|
||||
if (event == "removed") {
|
||||
cancelRequest();
|
||||
}
|
||||
};
|
||||
|
||||
let buttons = [
|
||||
{
|
||||
id: "styleeditor.confirmNavigationAway.buttonLeave",
|
||||
label: this.strings.GetStringFromName("confirmNavigationAway.buttonLeave"),
|
||||
accessKey: this.strings.GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"),
|
||||
callback: function onButtonLeave() {
|
||||
if (request) {
|
||||
request.resume();
|
||||
request = null;
|
||||
}
|
||||
}.bind(this),
|
||||
},
|
||||
{
|
||||
id: "styleeditor.confirmNavigationAway.buttonStay",
|
||||
label: this.strings.GetStringFromName("confirmNavigationAway.buttonStay"),
|
||||
accessKey: this.strings.GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
|
||||
callback: cancelRequest
|
||||
},
|
||||
];
|
||||
|
||||
let message = this.strings.GetStringFromName("confirmNavigationAway.message");
|
||||
|
||||
notification = notificationBox.appendNotification(message,
|
||||
"styleeditor-page-navigation", "chrome://browser/skin/Info.png",
|
||||
notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback);
|
||||
|
||||
// Make sure this not a transient notification, to avoid the automatic
|
||||
// transient notification removal.
|
||||
notification.persistence = -1;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* No window available anymore.
|
||||
*/
|
||||
@ -110,7 +189,7 @@ StyleEditorPanel.prototype = {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
|
||||
this._target.off("will-navigate", this.reset);
|
||||
this._target.off("will-navigate", this.beforeNavigate);
|
||||
this._target.off("navigate", this.newPage);
|
||||
this._target.off("close", this.destroy);
|
||||
this._target = null;
|
||||
@ -122,3 +201,9 @@ StyleEditorPanel.prototype = {
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(StyleEditorPanel.prototype, "strings",
|
||||
function () {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/styleeditor.properties");
|
||||
});
|
@ -27,6 +27,7 @@ _BROWSER_TEST_FILES = \
|
||||
browser_styleeditor_reopen.js \
|
||||
browser_styleeditor_sv_keynav.js \
|
||||
browser_styleeditor_sv_resize.js \
|
||||
browser_styleeditor_bug_826982_location_changed.js \
|
||||
head.js \
|
||||
helpers.js \
|
||||
four.html \
|
||||
|
@ -0,0 +1,123 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
|
||||
let TargetFactory = tempScope.TargetFactory;
|
||||
|
||||
function test() {
|
||||
let notificationBox, styleEditor;
|
||||
let alertActive1_called = false;
|
||||
let alertActive2_called = false;
|
||||
|
||||
function startLocationTests() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
||||
runTests(toolbox.getCurrentPanel(), toolbox);
|
||||
}).then(null, console.error);
|
||||
}
|
||||
|
||||
function runTests(aStyleEditor) {
|
||||
styleEditor = aStyleEditor;
|
||||
let para = content.document.querySelector("p");
|
||||
ok(para, "found the paragraph element");
|
||||
is(para.textContent, "init", "paragraph content is correct");
|
||||
|
||||
styleEditor.styleEditorChrome.markDirty();
|
||||
|
||||
notificationBox = gBrowser.getNotificationBox();
|
||||
notificationBox.addEventListener("AlertActive", alertActive1, false);
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
|
||||
|
||||
content.location = "data:text/html,<div>location change test 1 for " +
|
||||
"styleeditor</div><p>test1</p>";
|
||||
}
|
||||
|
||||
function alertActive1() {
|
||||
alertActive1_called = true;
|
||||
notificationBox.removeEventListener("AlertActive", alertActive1, false);
|
||||
|
||||
let notification = notificationBox.
|
||||
getNotificationWithValue("styleeditor-page-navigation");
|
||||
ok(notification, "found the styleeditor-page-navigation notification");
|
||||
|
||||
// By closing the notification it is expected that page navigation is
|
||||
// canceled.
|
||||
executeSoon(function() {
|
||||
notification.close();
|
||||
locationTest2();
|
||||
});
|
||||
}
|
||||
|
||||
function locationTest2() {
|
||||
// Location did not change.
|
||||
let para = content.document.querySelector("p");
|
||||
ok(para, "found the paragraph element, second time");
|
||||
is(para.textContent, "init", "paragraph content is correct");
|
||||
|
||||
notificationBox.addEventListener("AlertActive", alertActive2, false);
|
||||
|
||||
content.location = "data:text/html,<div>location change test 2 for " +
|
||||
"styleeditor</div><p>test2</p>";
|
||||
}
|
||||
|
||||
function alertActive2() {
|
||||
alertActive2_called = true;
|
||||
notificationBox.removeEventListener("AlertActive", alertActive2, false);
|
||||
|
||||
let notification = notificationBox.
|
||||
getNotificationWithValue("styleeditor-page-navigation");
|
||||
ok(notification, "found the styleeditor-page-navigation notification");
|
||||
|
||||
let buttons = notification.querySelectorAll("button");
|
||||
let buttonLeave = null;
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
if (buttons[i].buttonInfo.id == "styleeditor.confirmNavigationAway.buttonLeave") {
|
||||
buttonLeave = buttons[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ok(buttonLeave, "the Leave page button was found");
|
||||
|
||||
// Accept page navigation.
|
||||
executeSoon(function(){
|
||||
buttonLeave.doCommand();
|
||||
});
|
||||
}
|
||||
|
||||
function onPageLoad() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onPageLoad, true);
|
||||
|
||||
isnot(content.location.href.indexOf("test2"), -1,
|
||||
"page navigated to the correct location");
|
||||
|
||||
let para = content.document.querySelector("p");
|
||||
ok(para, "found the paragraph element, third time");
|
||||
is(para.textContent, "test2", "paragraph content is correct");
|
||||
|
||||
ok(alertActive1_called, "first notification box has been shown");
|
||||
ok(alertActive2_called, "second notification box has been shown");
|
||||
testEnd();
|
||||
}
|
||||
|
||||
|
||||
function testEnd() {
|
||||
notificationBox = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
executeSoon(finish);
|
||||
}
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
|
||||
waitForFocus(startLocationTests, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,<div>location change tests for " +
|
||||
"styleeditor.</div><p>init</p>";
|
||||
}
|
@ -83,3 +83,11 @@ ToolboxStyleEditor.label=Style Editor
|
||||
# This string is displayed in the tooltip of the tab when the debugger is
|
||||
# displayed inside the developer tools window.
|
||||
ToolboxStyleEditor.tooltip=CSS Stylesheets Editor
|
||||
|
||||
# LOCALIZATION NOTE (confirmNavigationAway): Shown in a notification box when
|
||||
# the user tries to navigate away from a web page.
|
||||
confirmNavigationAway.message=If you leave this page, unsaved changes in the Style Editor will be lost.
|
||||
confirmNavigationAway.buttonLeave=Leave Page
|
||||
confirmNavigationAway.buttonLeaveAccesskey=L
|
||||
confirmNavigationAway.buttonStay=Stay on Page
|
||||
confirmNavigationAway.buttonStayAccesskey=S
|
||||
|
Loading…
Reference in New Issue
Block a user