merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-10-21 12:08:10 +02:00
commit 2da785f89e
25 changed files with 675 additions and 119 deletions

View File

@ -37,7 +37,6 @@ All changes need a review by someone on the Jetpack review crew:
- [@mossop]
- [@gozala]
- [@ZER0]
- [@erikvold]
- [@jsantell]
- [@zombie]
@ -61,6 +60,5 @@ For API and developer ergonomics review, ask [@gozala].
[@mossop]:https://github.com/mossop/
[@gozala]:https://github.com/Gozala/
[@ZER0]:https://github.com/ZER0/
[@erikvold]:https://github.com/erikvold/
[@jsantell]:https://github.com/jsantell
[@zombie]:https://github.com/zombie

View File

@ -3,7 +3,6 @@ github
stackoverflow
bugzilla
irc
erikvold
jsantell
mossop
gozala

View File

@ -2,6 +2,7 @@
support-files =
file_dom_notifications.html
[browser_notification_do_not_disturb.js]
[browser_notification_open_settings.js]
[browser_notification_remove_permission.js]
skip-if = e10s

View File

@ -0,0 +1,91 @@
"use strict";
var tab;
var notification;
var notification2;
var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
function test () {
waitForExplicitFinish();
try {
// Only run the test if the do-not-disturb
// interface has been implemented.
ALERT_SERVICE.manualDoNotDisturb;
ok(true, "Alert service implements do-not-disturb interface");
} catch (e) {
ok(true, "Alert service doesn't implement do-not-disturb interface, exiting test");
finish();
return;
}
let pm = Services.perms;
registerCleanupFunction(function() {
ALERT_SERVICE.manualDoNotDisturb = false;
pm.remove(makeURI(notificationURL), "desktop-notification");
gBrowser.removeTab(tab);
window.restore();
});
pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
// Make sure that do-not-disturb is not enabled.
ok(!ALERT_SERVICE.manualDoNotDisturb, "Alert service should not be disabled when test starts");
ALERT_SERVICE.manualDoNotDisturb = false;
tab = gBrowser.addTab(notificationURL);
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("load", onLoad, true);
}
function onLoad() {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
notification = win.showNotification2();
notification.addEventListener("show", onAlertShowing);
}
function onAlertShowing() {
info("Notification alert showing");
notification.removeEventListener("show", onAlertShowing);
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
if (!alertWindow) {
ok(true, "Notifications don't use XUL windows on all platforms.");
notification.close();
finish();
return;
}
let doNotDisturbMenuItem = alertWindow.document.getElementById("doNotDisturbMenuItem");
is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found");
alertWindow.addEventListener("beforeunload", onAlertClosing);
doNotDisturbMenuItem.click();
info("Clicked on do-not-disturb menuitem")
}
function onAlertClosing(event) {
event.target.removeEventListener("beforeunload", onAlertClosing);
ok(ALERT_SERVICE.manualDoNotDisturb, "Alert service should be disabled after clicking menuitem");
let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
notification2 = win.showNotification2();
notification2.addEventListener("show", onAlert2Showing);
// The notification should not appear, but there is
// no way from the client-side to know that it was
// blocked, except for waiting some time and realizing
// that the "onshow" event never fired.
setTimeout(function() {
notification2.removeEventListener("show", onAlert2Showing);
finish();
}, 2000);
}
function onAlert2Showing() {
ok(false, "the second alert should not have been shown");
notification2.close();
}

View File

@ -2,6 +2,19 @@
* 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/. */
XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function () {
try {
let alertsService = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
// This will throw if manualDoNotDisturb isn't implemented.
alertsService.manualDoNotDisturb;
return alertsService;
} catch (ex) {
return undefined;
}
});
var gContentPane = {
init: function ()
{
@ -30,6 +43,18 @@ var gContentPane = {
}
}
let doNotDisturbAlertsEnabled = false;
if (AlertsServiceDND) {
let notificationsDoNotDisturbRow =
document.getElementById("notificationsDoNotDisturbRow");
notificationsDoNotDisturbRow.removeAttribute("hidden");
if (AlertsServiceDND.manualDoNotDisturb) {
let notificationsDoNotDisturb =
document.getElementById("notificationsDoNotDisturb");
notificationsDoNotDisturb.setAttribute("checked", true);
}
}
setEventListener("font.language.group", "change",
gContentPane._rebuildFonts);
setEventListener("notificationsPolicyButton", "command",
@ -46,6 +71,8 @@ var gContentPane = {
gContentPane.openTranslationProviderAttribution);
setEventListener("translateButton", "command",
gContentPane.showTranslationExceptions);
setEventListener("notificationsDoNotDisturb", "command",
gContentPane.toggleDoNotDisturbNotifications);
let drmInfoURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
@ -253,5 +280,10 @@ var gContentPane = {
{
Components.utils.import("resource:///modules/translation/Translation.jsm");
Translation.openProviderAttribution();
}
},
toggleDoNotDisturbNotifications: function (event)
{
AlertsServiceDND.manualDoNotDisturb = event.target.checked;
},
};

View File

@ -77,6 +77,15 @@
accesskey="&notificationsPolicyButton.accesskey;"/>
</hbox>
</row>
<row id="notificationsDoNotDisturbRow" hidden="true">
<vbox align="start">
<checkbox id="notificationsDoNotDisturb" label="&notificationsDoNotDisturb.label;"
accesskey="&notificationsDoNotDisturb.accesskey;"/>
<label id="notificationsDoNotDisturbDetails"
class="indent"
value="&notificationsDoNotDisturbDetails.value;"/>
</vbox>
</row>
</rows>
</grid>
</groupbox>

View File

@ -20,6 +20,7 @@ skip-if = os != "win" # This test tests the windows-specific app selection dialo
[browser_cookies_exceptions.js]
[browser_healthreport.js]
skip-if = !healthreport || (os == 'linux' && debug)
[browser_notifications_do_not_disturb.js]
[browser_permissions.js]
[browser_proxy_backup.js]
[browser_privacypane_1.js]

View File

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
registerCleanupFunction(function() {
while (gBrowser.tabs[1])
gBrowser.removeTab(gBrowser.tabs[1]);
});
add_task(function() {
let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent", undefined, {leaveOpen: true});
is(prefs.selectedPane, "paneContent", "Content pane was selected");
let doc = gBrowser.contentDocument;
let notificationsDoNotDisturbRow = doc.getElementById("notificationsDoNotDisturbRow");
if (notificationsDoNotDisturbRow.hidden) {
todo(false, "Do not disturb is not available on this platform");
return;
}
let alertService;
try {
alertService = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
} catch (ex) {
ok(true, "Do not disturb is not available on this platform: " + ex.message);
return;
}
let checkbox = doc.getElementById("notificationsDoNotDisturb");
ok(!checkbox.checked, "Checkbox should not be checked by default");
ok(!alertService.manualDoNotDisturb, "Do not disturb should be off by default");
let checkboxChanged = waitForEvent(checkbox, "command")
checkbox.click();
yield checkboxChanged;
ok(alertService.manualDoNotDisturb, "Do not disturb should be enabled when checked");
checkboxChanged = waitForEvent(checkbox, "command")
checkbox.click();
yield checkboxChanged;
ok(!alertService.manualDoNotDisturb, "Do not disturb should be disabled when unchecked");
});

View File

@ -79,6 +79,32 @@
current node -->
<!ENTITY inspectorHTMLDelete.label "Delete Node">
<!ENTITY inspectorHTMLDelete.accesskey "D">
<!-- LOCALIZATION NOTE (inspectorAttributeSubmenu.label): This is the label
shown in the inspector contextual-menu for the sub-menu of the other
attribute items, which allow to:
- add new attribute
- edit attribute
- remove attribute -->
<!ENTITY inspectorAttributeSubmenu.label "Attribute">
<!ENTITY inspectorAttributeSubmenu.accesskey "A">
<!-- LOCALIZATION NOTE (inspectorAddAttribute.label): This is the label shown in
the inspector contextual-menu for the item that lets users add attribute
to current node -->
<!ENTITY inspectorAddAttribute.label "Add Attribute">
<!ENTITY inspectorAddAttribute.accesskey "A">
<!-- LOCALIZATION NOTE (inspectorEditAttribute.label): This is the label shown in
the inspector contextual-menu for the item that lets users edit attribute
for current node -->
<!ENTITY inspectorEditAttribute.label "Edit Attribute">
<!ENTITY inspectorEditAttribute.accesskey "E">
<!-- LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label shown in
the inspector contextual-menu for the item that lets users delete attribute
from current node -->
<!ENTITY inspectorRemoveAttribute.label "Remove Attribute">
<!ENTITY inspectorRemoveAttribute.accesskey "R">
<!ENTITY inspector.selectButton.tooltip "Select element with mouse">

View File

@ -104,3 +104,15 @@ inspector.menu.copyUrlToClipboard.label=Copy Link Address
# element in the DOM (like with <label for="input-id">), and that allows to
# select that element in the inspector.
inspector.menu.selectElement.label=Select Element #%S
# LOCALIZATION NOTE (inspector.menu.editAttribute.label): This is the label of a
# sub-menu "Attribute" in the inspector contextual-menu that appears
# when the user right-clicks on the node in the inspector, and that allows
# to edit an attribute on this node.
inspector.menu.editAttribute.label=Edit Attribute %S
# LOCALIZATION NOTE (inspector.menu.removeAttribute.label): This is the label of a
# sub-menu "Attribute" in the inspector contextual-menu that appears
# when the user right-clicks on the attribute of a node in the inspector,
# and that allows to remove this attribute.
inspector.menu.removeAttribute.label=Remove Attribute %S

View File

@ -11,6 +11,9 @@
<!ENTITY notificationsPolicyDesc.label "Choose which sites are allowed to show notifications">
<!ENTITY notificationsPolicyButton.accesskey "h">
<!ENTITY notificationsPolicyButton.label "Choose…">
<!ENTITY notificationsDoNotDisturb.label "Do not disturb me">
<!ENTITY notificationsDoNotDisturb.accesskey "n">
<!ENTITY notificationsDoNotDisturbDetails.value "No notification will be shown until you restart &brandShortName;">
<!ENTITY popupExceptions.label "Exceptions…">
<!ENTITY popupExceptions.accesskey "E">

View File

@ -271,7 +271,9 @@ description > html|a {
}
.indent {
-moz-margin-start: 33px;
/* !important needed to override -moz-margin-start:0 !important; rule
define in common.css for labels */
-moz-margin-start: 33px !important;
}
.text-link {

View File

@ -77,6 +77,7 @@ function InspectorPanel(iframeWindow, toolbox) {
this.panelWin = iframeWindow;
this.panelWin.inspector = this;
this.nodeMenuTriggerInfo = null;
this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
this._target.on("will-navigate", this._onBeforeNavigate);
@ -647,9 +648,14 @@ InspectorPanel.prototype = {
},
/**
* Disable the delete item if needed. Update the pseudo classes.
* Update, enable, disable, hide, show any menu item depending on the current
* element.
*/
_setupNodeMenu: function() {
_setupNodeMenu: function(event) {
let markupContainer = this.markup.getContainer(this.selection.nodeFront);
this.nodeMenuTriggerInfo =
markupContainer.editor.getInfoAtNode(event.target.triggerNode);
let isSelectionElement = this.selection.isElementNode() &&
!this.selection.isPseudoElementNode();
let isEditableElement = isSelectionElement &&
@ -696,9 +702,8 @@ InspectorPanel.prototype = {
expandAll.setAttribute("disabled", "true");
collapse.setAttribute("disabled", "true");
let markUpContainer = this.markup.importNode(this.selection.nodeFront, false);
if (this.selection.isNode() && markUpContainer.hasChildren) {
if (markUpContainer.expanded) {
if (this.selection.isNode() && markupContainer.hasChildren) {
if (markupContainer.expanded) {
collapse.removeAttribute("disabled");
}
expandAll.removeAttribute("disabled");
@ -784,12 +789,52 @@ InspectorPanel.prototype = {
// Enable the "copy image data-uri" item if the selection is previewable
// which essentially checks if it's an image or canvas tag
let copyImageData = this.panelDoc.getElementById("node-menu-copyimagedatauri");
let markupContainer = this.markup.getContainer(this.selection.nodeFront);
if (isSelectionElement && markupContainer && markupContainer.isPreviewable()) {
copyImageData.removeAttribute("disabled");
} else {
copyImageData.setAttribute("disabled", "true");
}
// Enable / disable "Add Attribute", "Edit Attribute"
// and "Remove Attribute" items
this._setupAttributeMenu(isEditableElement);
},
_setupAttributeMenu: function(isEditableElement) {
let addAttribute = this.panelDoc.getElementById("node-menu-add-attribute");
let editAttribute = this.panelDoc.getElementById("node-menu-edit-attribute");
let removeAttribute = this.panelDoc.getElementById("node-menu-remove-attribute");
let nodeInfo = this.nodeMenuTriggerInfo;
// Enable "Add Attribute" for all editable elements
if (isEditableElement) {
addAttribute.removeAttribute("disabled");
} else {
addAttribute.setAttribute("disabled", "true");
}
// Enable "Edit Attribute" and "Remove Attribute" only on attribute click
if (isEditableElement && nodeInfo && nodeInfo.type === "attribute") {
editAttribute.removeAttribute("disabled");
editAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.editAttribute.label", [`"${nodeInfo.name}"`], 1));
removeAttribute.removeAttribute("disabled");
removeAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.removeAttribute.label", [`"${nodeInfo.name}"`], 1));
} else {
editAttribute.setAttribute("disabled", "true");
editAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.editAttribute.label", [''], 1));
removeAttribute.setAttribute("disabled", "true");
removeAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.removeAttribute.label", [''], 1));
}
},
_resetNodeMenu: function() {
@ -1224,6 +1269,33 @@ InspectorPanel.prototype = {
}
},
/**
* Add attribute to node.
* Used for node context menu and shouldn't be called directly.
*/
onAddAttribute: function() {
let container = this.markup.getContainer(this.selection.nodeFront);
container.addAttribute();
},
/**
* Edit attribute for node.
* Used for node context menu and shouldn't be called directly.
*/
onEditAttribute: function() {
let container = this.markup.getContainer(this.selection.nodeFront);
container.editAttribute(this.nodeMenuTriggerInfo.name);
},
/**
* Remove attribute from node.
* Used for node context menu and shouldn't be called directly.
*/
onRemoveAttribute: function() {
let container = this.markup.getContainer(this.selection.nodeFront);
container.removeAttribute(this.nodeMenuTriggerInfo.name);
},
expandNode: function() {
this.markup.expandAll(this.selection.nodeFront);
},

View File

@ -110,6 +110,23 @@
label="&inspectorHTMLDelete.label;"
accesskey="&inspectorHTMLDelete.accesskey;"
oncommand="inspector.deleteNode()"/>
<menu label="&inspectorAttributeSubmenu.label;"
accesskey="&inspectorAttributeSubmenu.accesskey;">
<menupopup>
<menuitem id="node-menu-add-attribute"
label="&inspectorAddAttribute.label;"
accesskey="&inspectorAddAttribute.accesskey;"
oncommand="inspector.onAddAttribute()"/>
<menuitem id="node-menu-edit-attribute"
label="&inspectorEditAttribute.label;"
accesskey="&inspectorEditAttribute.accesskey;"
oncommand="inspector.onEditAttribute()"/>
<menuitem id="node-menu-remove-attribute"
label="&inspectorRemoveAttribute.label;"
accesskey="&inspectorRemoveAttribute.accesskey;"
oncommand="inspector.onRemoveAttribute()"/>
</menupopup>
</menu>
<menuseparator id="node-menu-link-separator"/>
<menuitem id="node-menu-link-follow"
oncommand="inspector.onFollowLink()"/>

View File

@ -86,7 +86,8 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_menu-02-copy-items.js]
[browser_inspector_menu-03-paste-items.js]
[browser_inspector_menu-04-use-in-console.js]
[browser_inspector_menu-05-other.js]
[browser_inspector_menu-05-attribute-items.js]
[browser_inspector_menu-06-other.js]
[browser_inspector_navigation.js]
[browser_inspector_pane-toggle-01.js]
[browser_inspector_pane-toggle-02.js]

View File

@ -32,12 +32,24 @@ const ALL_MENU_ITEMS = [
"node-menu-pseudo-active",
"node-menu-pseudo-focus",
"node-menu-scrollnodeintoview",
"node-menu-screenshotnode"
"node-menu-screenshotnode",
"node-menu-add-attribute",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS);
const INACTIVE_ON_DOCTYPE_ITEMS =
ALL_MENU_ITEMS.filter(item => ACTIVE_ON_DOCTYPE_ITEMS.indexOf(item) === -1);
/**
* Test cases, each item of this array may define the following properties:
* desc: string that will be logged
* selector: selector of the node to be selected
* disabled: items that should have disabled state
* clipboardData: clipboard content
* clipboardDataType: clipboard content type
* attributeTrigger: attribute that will be used as context menu trigger
*/
const TEST_CASES = [
{
desc: "doctype node with empty clipboard",
@ -55,7 +67,11 @@ const TEST_CASES = [
desc: "element node HTML on the clipboard",
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
disabled: ["node-menu-copyimagedatauri"],
disabled: [
"node-menu-copyimagedatauri",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
],
selector: "#sensitivity",
},
{
@ -69,6 +85,8 @@ const TEST_CASES = [
"node-menu-pasteafter",
"node-menu-pastefirstchild",
"node-menu-pastelastchild",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
],
},
{
@ -80,6 +98,8 @@ const TEST_CASES = [
"node-menu-copyimagedatauri",
"node-menu-pastebefore",
"node-menu-pasteafter",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]
},
{
@ -87,7 +107,10 @@ const TEST_CASES = [
clipboardData: "<p>some text</p>",
clipboardDataType: "html",
selector: "img",
disabled: []
disabled: [
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]
},
{
desc: "<head> with HTML on clipboard",
@ -99,6 +122,8 @@ const TEST_CASES = [
"node-menu-pastebefore",
"node-menu-pasteafter",
"node-menu-screenshotnode",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
],
},
{
@ -107,6 +132,8 @@ const TEST_CASES = [
disabled: PASTE_MENU_ITEMS.concat([
"node-menu-copyimagedatauri",
"node-menu-screenshotnode",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]),
},
{
@ -114,7 +141,11 @@ const TEST_CASES = [
clipboardData: "some text",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: ["node-menu-copyimagedatauri"],
disabled: [
"node-menu-copyimagedatauri",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]
},
{
desc: "<element> with base64 encoded image data uri on clipboard",
@ -123,21 +154,33 @@ const TEST_CASES = [
"AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
disabled: PASTE_MENU_ITEMS.concat([
"node-menu-copyimagedatauri",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]),
},
{
desc: "<element> with empty string on clipboard",
clipboardData: "",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
disabled: PASTE_MENU_ITEMS.concat([
"node-menu-copyimagedatauri",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]),
},
{
desc: "<element> with whitespace only on clipboard",
clipboardData: " \n\n\t\n\n \n",
clipboardDataType: undefined,
selector: "#paste-area",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
disabled: PASTE_MENU_ITEMS.concat([
"node-menu-copyimagedatauri",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]),
},
{
desc: "<element> that isn't visible on the page, empty clipboard",
@ -145,6 +188,8 @@ const TEST_CASES = [
disabled: PASTE_MENU_ITEMS.concat([
"node-menu-copyimagedatauri",
"node-menu-screenshotnode",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]),
},
{
@ -153,7 +198,15 @@ const TEST_CASES = [
disabled: PASTE_MENU_ITEMS.concat([
"node-menu-copyimagedatauri",
"node-menu-screenshotnode",
"node-menu-edit-attribute",
"node-menu-remove-attribute"
]),
},
{
desc: "<element> with context menu triggered on attribute, empty clipboard",
selector: "#attributes",
disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
attributeTrigger: "data-edit"
}
];
@ -165,7 +218,7 @@ registerCleanupFunction(() => {
add_task(function *() {
let { inspector } = yield openInspectorForURL(TEST_URL);
for (let test of TEST_CASES) {
let { desc, disabled, selector } = test;
let { desc, disabled, selector, attributeTrigger } = test;
info(`Test ${desc}`);
setupClipboard(test.clipboardData, test.clipboardDataType);
@ -176,7 +229,11 @@ add_task(function *() {
yield selectNode(front, inspector);
info("Simulating context menu click on the selected node container.");
contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
let nodeFrontContainer = getContainerForNodeFront(front, inspector);
let contextMenuTrigger = attributeTrigger
? nodeFrontContainer.tagLine.querySelector(`[data-attr="${attributeTrigger}"]`)
: nodeFrontContainer.tagLine;
contextMenuClick(contextMenuTrigger);
for (let menuitem of ALL_MENU_ITEMS) {
let elt = inspector.panelDoc.getElementById(menuitem);

View File

@ -0,0 +1,75 @@
/* 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";
// Test that attribute items work in the context menu
const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
add_task(function* () {
let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URL);
yield selectNode("#attributes", inspector);
yield testAddAttribute();
yield testEditAttribute();
yield testRemoveAttribute();
function* testAddAttribute() {
info("Testing 'Add Attribute' menu item");
let addAttribute = getMenuItem("node-menu-add-attribute");
info("Triggering 'Add Attribute' and waiting for mutation to occur");
dispatchCommandEvent(addAttribute);
EventUtils.synthesizeKey('class="u-hidden"', {});
let onMutation = inspector.once("markupmutation");
EventUtils.synthesizeKey('VK_RETURN', {});
yield onMutation;
let hasAttribute = testActor.hasNode("#attributes.u-hidden");
ok(hasAttribute, "attribute was successfully added");
}
function* testEditAttribute() {
info("Testing 'Edit Attribute' menu item");
let editAttribute = getMenuItem("node-menu-edit-attribute");
info("Triggering 'Edit Attribute' and waiting for mutation to occur");
inspector.nodeMenuTriggerInfo = {
type: "attribute",
name: "data-edit"
};
dispatchCommandEvent(editAttribute);
EventUtils.synthesizeKey("data-edit='edited'", {});
let onMutation = inspector.once("markupmutation");
EventUtils.synthesizeKey('VK_RETURN', {});
yield onMutation;
let isAttributeChanged =
yield testActor.hasNode("#attributes[data-edit='edited']");
ok(isAttributeChanged, "attribute was successfully edited");
}
function* testRemoveAttribute() {
info("Testing 'Remove Attribute' menu item");
let removeAttribute = getMenuItem("node-menu-remove-attribute");
info("Triggering 'Remove Attribute' and waiting for mutation to occur");
inspector.nodeMenuTriggerInfo = {
type: "attribute",
name: "data-remove"
};
let onMutation = inspector.once("markupmutation");
dispatchCommandEvent(removeAttribute);
yield onMutation;
let hasAttribute = yield testActor.hasNode("#attributes[data-remove]")
ok(!hasAttribute, "attribute was successfully removed");
}
function getMenuItem(id) {
let attribute = inspector.panelDoc.getElementById(id);
ok(attribute, "Menu item '" + id + "' found");
return attribute;
}
});

View File

@ -23,6 +23,7 @@
</div>
<p id="console-var">Paragraph for testing console variables</p>
<p id="console-var-multi">Paragraph for testing multiple console variables</p>
<p id="attributes" data-edit="original" data-remove="thing">Attributes are going to be changed here</p>
</div>
</body>
</html>

View File

@ -2305,6 +2305,36 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
clearSingleTextChild: function() {
this.singleTextChild = undefined;
this.editor.updateTextEditor();
},
/**
* Trigger new attribute field for input.
*/
addAttribute: function() {
this.editor.newAttr.editMode();
},
/**
* Trigger attribute field for editing.
*/
editAttribute: function(attrName) {
this.editor.attrElements.get(attrName).editMode();
},
/**
* Remove attribute from container.
* This is an undoable action.
*/
removeAttribute: function(attrName) {
let doMods = this.editor._startModifyingAttributes();
let undoMods = this.editor._startModifyingAttributes();
this.editor._saveAttribute(attrName, undoMods);
doMods.removeAttribute(attrName);
this.undo.do(() => {
doMods.apply();
}, () => {
undoMods.apply();
});
}
});
@ -2361,6 +2391,13 @@ function GenericEditor(aContainer, aNode) {
GenericEditor.prototype = {
destroy: function() {
this.elt.remove();
},
/**
* Stub method for consistency with ElementEditor.
*/
getInfoAtNode: function() {
return null;
}
};
@ -2443,7 +2480,14 @@ TextEditor.prototype = {
}
},
destroy: function() {}
destroy: function() {},
/**
* Stub method for consistency with ElementEditor.
*/
getInfoAtNode: function() {
return null;
}
};
/**
@ -2497,18 +2541,14 @@ function ElementEditor(aContainer, aNode) {
return;
}
try {
let doMods = this._startModifyingAttributes();
let undoMods = this._startModifyingAttributes();
this._applyAttributes(aVal, null, doMods, undoMods);
this.container.undo.do(() => {
doMods.apply();
}, function() {
undoMods.apply();
});
} catch(x) {
console.error(x);
}
let doMods = this._startModifyingAttributes();
let undoMods = this._startModifyingAttributes();
this._applyAttributes(aVal, null, doMods, undoMods);
this.container.undo.do(() => {
doMods.apply();
}, function() {
undoMods.apply();
});
}
});
@ -2540,6 +2580,35 @@ ElementEditor.prototype = {
flashElementOff(this.getAttributeElement(attrName));
}, this.markup.CONTAINER_FLASHING_DURATION);
},
/**
* Returns information about node in the editor.
*
* @param {DOMNode} node
* The node to get information from.
*
* @return {Object}
* An object literal with the following information:
* {type: "attribute", name: "rel", value: "index", el: node}
*/
getInfoAtNode: function(node) {
if (!node) {
return null;
}
let type = null;
let name = null;
let value = null;
// Attribute
let attribute = node.closest('.attreditor');
if (attribute) {
type = "attribute";
name = attribute.querySelector('.attr-name').textContent;
value = attribute.querySelector('.attr-value').textContent;
}
return {type, name, value, el: node};
},
/**
* Update the state of the editor from the node.
@ -2697,19 +2766,15 @@ ElementEditor.prototype = {
// Remove the attribute stored in this editor and re-add any attributes
// parsed out of the input element. Restore original attribute if
// parsing fails.
try {
this.refocusOnEdit(aAttr.name, attr, direction);
this._saveAttribute(aAttr.name, undoMods);
doMods.removeAttribute(aAttr.name);
this._applyAttributes(aVal, attr, doMods, undoMods);
this.container.undo.do(() => {
doMods.apply();
}, () => {
undoMods.apply();
});
} catch(ex) {
console.error(ex);
}
this.refocusOnEdit(aAttr.name, attr, direction);
this._saveAttribute(aAttr.name, undoMods);
doMods.removeAttribute(aAttr.name);
this._applyAttributes(aVal, attr, doMods, undoMods);
this.container.undo.do(() => {
doMods.apply();
}, () => {
undoMods.apply();
});
}
});

View File

@ -209,7 +209,6 @@ public class BrowserApp extends GeckoApp
private static class MenuItemInfo {
public int id;
public String label;
public String icon;
public boolean checkable;
public boolean checked;
public boolean enabled = true;
@ -1763,7 +1762,6 @@ public class BrowserApp extends GeckoApp
final MenuItemInfo info = new MenuItemInfo();
info.label = message.getString("name");
info.id = message.getInt("id") + ADDON_MENU_OFFSET;
info.icon = message.optString("icon", null);
info.checked = message.optBoolean("checked", false);
info.enabled = message.optBoolean("enabled", true);
info.visible = message.optBoolean("visible", true);

View File

@ -2317,9 +2317,9 @@ var NativeWindow = {
if (arguments.length == 1) {
options = arguments[0];
} else if (arguments.length == 3) {
Log.w("Browser", "This menu addon API has been deprecated. Instead, use the options object API.");
options = {
name: arguments[0],
icon: arguments[1],
callback: arguments[2]
};
} else {
@ -5842,6 +5842,8 @@ var HealthReportStatusListener = {
var XPInstallObserver = {
init: function() {
Services.obs.addObserver(this, "addon-install-origin-blocked", false);
Services.obs.addObserver(this, "addon-install-disabled", false);
Services.obs.addObserver(this, "addon-install-blocked", false);
Services.obs.addObserver(this, "addon-install-started", false);
Services.obs.addObserver(this, "xpi-signature-changed", false);
@ -5851,76 +5853,103 @@ var XPInstallObserver = {
},
observe: function(aSubject, aTopic, aData) {
let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
let tab = BrowserApp.getTabForBrowser(installInfo.browser);
let strings = Strings.browser;
let host = null;
if (installInfo.originatingURI) {
host = installInfo.originatingURI.host;
}
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
switch (aTopic) {
case "addon-install-started":
NativeWindow.toast.show(Strings.browser.GetStringFromName("alertAddonsDownloading"), "short");
NativeWindow.toast.show(strings.GetStringFromName("alertAddonsDownloading"), "short");
break;
case "addon-install-blocked": {
let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
let tab = BrowserApp.getTabForBrowser(installInfo.browser);
case "addon-install-disabled": {
if (!tab)
return;
let host = null;
if (installInfo.originatingURI) {
host = installInfo.originatingURI.host;
}
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
let notificationName, buttons, message;
let strings = Strings.browser;
let enabled = true;
try {
enabled = Services.prefs.getBoolPref("xpinstall.enabled");
}
catch (e) {}
} catch (e) {}
let buttons, message, callback;
if (!enabled) {
notificationName = "xpinstall-disabled";
if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
message = strings.GetStringFromName("xpinstallDisabledMessageLocked");
buttons = [];
} else {
message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2);
buttons = [{
label: strings.GetStringFromName("xpinstallDisabledButton"),
callback: function editPrefs() {
Services.prefs.setBoolPref("xpinstall.enabled", true);
return false;
}
}];
}
message = strings.GetStringFromName("xpinstallDisabledMessageLocked");
buttons = [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")];
callback: (data) => {};
} else {
notificationName = "xpinstall";
if (host) {
// We have a host which asked for the install.
message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
} else {
// Without a host we address the add-on as the initiator of the install.
let addon = null;
if (installInfo.installs.length > 0) {
addon = installInfo.installs[0].name;
message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2);
buttons = [
strings.GetStringFromName("xpinstallDisabledButton"),
strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
];
callback: (data) => {
if (data.button === 1) {
Services.prefs.setBoolPref("xpinstall.enabled", true)
}
if (addon) {
// We have an addon name, show the regular message.
message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
} else {
// We don't have an addon name, show an alternative message.
message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
}
}
buttons = [{
label: strings.GetStringFromName("xpinstallPromptAllowButton"),
callback: function() {
// Kick off the install
installInfo.install();
return false;
},
positive: true
}];
};
}
NativeWindow.doorhanger.show(message, aTopic, buttons, tab.id);
new Prompt({
title: Strings.browser.GetStringFromName("addonError.titleError"),
message: message,
buttons: buttons
}).show(callback);
break;
}
case "addon-install-blocked": {
if (!tab)
return;
let message;
if (host) {
// We have a host which asked for the install.
message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
} else {
// Without a host we address the add-on as the initiator of the install.
let addon = null;
if (installInfo.installs.length > 0) {
addon = installInfo.installs[0].name;
}
if (addon) {
// We have an addon name, show the regular message.
message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
} else {
// We don't have an addon name, show an alternative message.
message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
}
}
let buttons = [
strings.GetStringFromName("xpinstallPromptAllowButton"),
strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
];
new Prompt({
title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
message: message,
buttons: buttons
}).show((data) => {
if (data.button === 0) {
// Kick off the install
installInfo.install();
}
});
break;
}
case "addon-install-origin-blocked": {
if (!tab)
return;
new Prompt({
title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
message: strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1),
buttons: [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")]
}).show((data) => {});
break;
}
case "xpi-signature-changed": {

View File

@ -44,11 +44,19 @@ function prefillAlertInfo() {
let hostPort = window.arguments[10];
const ALERT_BUNDLE = Services.strings.createBundle(
"chrome://alerts/locale/alert.properties");
const BRAND_BUNDLE = Services.strings.createBundle(
"chrome://branding/locale/brand.properties");
const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
let label = document.getElementById("alertSourceLabel");
label.setAttribute("value",
ALERT_BUNDLE.formatStringFromName("source.label",
[hostPort],
1));
let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem");
doNotDisturbMenuItem.setAttribute("label",
ALERT_BUNDLE.formatStringFromName("doNotDisturb.label",
[BRAND_NAME],
1));
let disableForOrigin = document.getElementById("disableForOriginMenuItem");
disableForOrigin.setAttribute("label",
ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
@ -62,12 +70,12 @@ function prefillAlertInfo() {
gReplacedWindow = window.arguments[8];
case 8:
if (window.arguments[7]) {
document.getElementById('alertTitleLabel').setAttribute('lang', window.arguments[7]);
document.getElementById('alertTextLabel').setAttribute('lang', window.arguments[7]);
document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]);
document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]);
}
case 7:
if (window.arguments[6]) {
document.getElementById('alertNotification').style.direction = window.arguments[6];
document.getElementById("alertNotification").style.direction = window.arguments[6];
}
case 6:
gOrigin = window.arguments[5];
@ -76,20 +84,20 @@ function prefillAlertInfo() {
case 4:
gAlertTextClickable = window.arguments[3];
if (gAlertTextClickable) {
document.getElementById('alertNotification').setAttribute('clickable', true);
document.getElementById('alertTextLabel').setAttribute('clickable', true);
document.getElementById("alertNotification").setAttribute("clickable", true);
document.getElementById("alertTextLabel").setAttribute("clickable", true);
}
case 3:
if (window.arguments[2]) {
document.getElementById('alertBox').setAttribute('hasBodyText', true);
document.getElementById('alertTextLabel').textContent = window.arguments[2];
document.getElementById("alertBox").setAttribute("hasBodyText", true);
document.getElementById("alertTextLabel").textContent = window.arguments[2];
}
case 2:
document.getElementById('alertTitleLabel').textContent = window.arguments[1];
document.getElementById("alertTitleLabel").textContent = window.arguments[1];
case 1:
if (window.arguments[0]) {
document.getElementById('alertBox').setAttribute('hasImage', true);
document.getElementById('alertImage').setAttribute('src', window.arguments[0]);
document.getElementById("alertBox").setAttribute("hasImage", true);
document.getElementById("alertImage").setAttribute("src", window.arguments[0]);
}
case 0:
break;
@ -142,7 +150,7 @@ function moveWindowToReplace(aReplacedAlert) {
// Move windows that come after the replaced alert if the height is different.
if (heightDelta != 0) {
let windows = Services.wm.getEnumerator('alert:alert');
let windows = Services.wm.getEnumerator("alert:alert");
while (windows.hasMoreElements()) {
let alertWindow = windows.getNext();
// boolean to determine if the alert window is after the replaced alert.
@ -172,7 +180,7 @@ function moveWindowToEnd() {
screen.availTop + screen.availHeight - window.outerHeight;
// Position the window at the end of all alerts.
let windows = Services.wm.getEnumerator('alert:alert');
let windows = Services.wm.getEnumerator("alert:alert");
while (windows.hasMoreElements()) {
let alertWindow = windows.getNext();
if (alertWindow != window) {
@ -195,7 +203,7 @@ function onAlertBeforeUnload() {
if (!gIsReplaced) {
// Move other alert windows to fill the gap left by closing alert.
let heightDelta = window.outerHeight + WINDOW_MARGIN;
let windows = Services.wm.getEnumerator('alert:alert');
let windows = Services.wm.getEnumerator("alert:alert");
while (windows.hasMoreElements()) {
let alertWindow = windows.getNext();
if (alertWindow != window) {
@ -231,6 +239,14 @@ function onAlertClick() {
}
}
function doNotDisturb() {
const alertService = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
alertService.manualDoNotDisturb = true;
onAlertClose();
}
function disableForOrigin() {
gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
onAlertClose();

View File

@ -46,6 +46,9 @@
<button type="menu" id="alertSettings" tooltiptext="&settings.label;"
onclick="event.stopPropagation();">
<menupopup position="after_end">
<menuitem id="doNotDisturbMenuItem"
oncommand="doNotDisturb();"/>
<menuseparator/>
<menuitem id="disableForOriginMenuItem"
oncommand="disableForOrigin();"/>
</menupopup>

View File

@ -17,3 +17,7 @@ webActions.disableForOrigin.label = Disable notifications from %S
# and port.
source.label=via %1$S
webActions.settings.label = Notification settings
# LOCALIZATION NOTE(doNotDisturb.label): %S is replaced with the
# brandShortName of the application.
doNotDisturb.label = Do not disturb me until I restart %S