Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-08-20 16:27:00 -04:00
commit 5228552c40
39 changed files with 747 additions and 271 deletions

View File

@ -49,7 +49,11 @@ const EXPECTED_REFLOWS = [
// tabPreviews.capture()
"tabPreviews_capture@chrome://browser/content/browser.js|" +
"tabPreviews_handleEvent/<@chrome://browser/content/browser.js|"
"tabPreviews_handleEvent/<@chrome://browser/content/browser.js|",
// tabPreviews.capture()
"tabPreviews_capture@chrome://browser/content/browser.js|" +
"@chrome://browser/content/browser.js|"
];
const PREF_PRELOAD = "browser.newtab.preload";

View File

@ -1219,81 +1219,102 @@ ElementEditor.prototype = {
return this.node.startModifyingAttributes();
},
_createAttribute: function EE_createAttribute(aAttr, aBefore)
_createAttribute: function EE_createAttribute(aAttr, aBefore = null)
{
if (this.attrs.hasOwnProperty(aAttr.name)) {
var attr = this.attrs[aAttr.name];
var name = attr.querySelector(".attrname");
var val = attr.querySelector(".attrvalue");
} else {
// Create the template editor, which will save some variables here.
let data = {
attrName: aAttr.name,
};
this.template("attribute", data);
var {attr, inner, name, val} = data;
// Create the template editor, which will save some variables here.
let data = {
attrName: aAttr.name,
};
this.template("attribute", data);
var {attr, inner, name, val} = data;
// Figure out where we should place the attribute.
let before = aBefore || null;
if (aAttr.name == "id") {
before = this.attrList.firstChild;
} else if (aAttr.name == "class") {
let idNode = this.attrs["id"];
before = idNode ? idNode.nextSibling : this.attrList.firstChild;
}
this.attrList.insertBefore(attr, before);
// Double quotes need to be handled specially to prevent DOMParser failing.
// name="v"a"l"u"e" when editing -> name='v"a"l"u"e"'
// name="v'a"l'u"e" when editing -> name="v'a&quot;l'u&quot;e"
let editValueDisplayed = aAttr.value;
let hasDoubleQuote = editValueDisplayed.contains('"');
let hasSingleQuote = editValueDisplayed.contains("'");
let initial = aAttr.name + '="' + editValueDisplayed + '"';
// Make the attribute editable.
editableField({
element: inner,
trigger: "dblclick",
stopOnReturn: true,
selectAll: false,
contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
popup: this.markup.popup,
start: (aEditor, aEvent) => {
// If the editing was started inside the name or value areas,
// select accordingly.
if (aEvent && aEvent.target === name) {
aEditor.input.setSelectionRange(0, name.textContent.length);
} else if (aEvent && aEvent.target === val) {
let length = val.textContent.length;
let editorLength = aEditor.input.value.length;
let start = editorLength - (length + 1);
aEditor.input.setSelectionRange(start, start + length);
} else {
aEditor.input.select();
}
},
done: (aVal, aCommit) => {
if (!aCommit) {
return;
}
let doMods = this._startModifyingAttributes();
let undoMods = this._startModifyingAttributes();
// 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._saveAttribute(aAttr.name, undoMods);
doMods.removeAttribute(aAttr.name);
this._applyAttributes(aVal, attr, doMods, undoMods);
this.undo.do(() => {
doMods.apply();
}, () => {
undoMods.apply();
})
} catch(ex) {
console.error(ex);
}
}
});
this.attrs[aAttr.name] = attr;
// Can't just wrap value with ' since the value contains both " and '.
if (hasDoubleQuote && hasSingleQuote) {
editValueDisplayed = editValueDisplayed.replace(/\"/g, "&quot;");
initial = aAttr.name + '="' + editValueDisplayed + '"';
}
// Wrap with ' since there are no single quotes in the attribute value.
if (hasDoubleQuote && !hasSingleQuote) {
initial = aAttr.name + "='" + editValueDisplayed + "'";
}
// Make the attribute editable.
editableField({
element: inner,
trigger: "dblclick",
stopOnReturn: true,
selectAll: false,
initial: initial,
contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
popup: this.markup.popup,
start: (aEditor, aEvent) => {
// If the editing was started inside the name or value areas,
// select accordingly.
if (aEvent && aEvent.target === name) {
aEditor.input.setSelectionRange(0, name.textContent.length);
} else if (aEvent && aEvent.target === val) {
let length = editValueDisplayed.length;
let editorLength = aEditor.input.value.length;
let start = editorLength - (length + 1);
aEditor.input.setSelectionRange(start, start + length);
} else {
aEditor.input.select();
}
},
done: (aVal, aCommit) => {
if (!aCommit) {
return;
}
let doMods = this._startModifyingAttributes();
let undoMods = this._startModifyingAttributes();
// 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._saveAttribute(aAttr.name, undoMods);
doMods.removeAttribute(aAttr.name);
this._applyAttributes(aVal, attr, doMods, undoMods);
this.undo.do(() => {
doMods.apply();
}, () => {
undoMods.apply();
})
} catch(ex) {
console.error(ex);
}
}
});
// Figure out where we should place the attribute.
let before = aBefore;
if (aAttr.name == "id") {
before = this.attrList.firstChild;
} else if (aAttr.name == "class") {
let idNode = this.attrs["id"];
before = idNode ? idNode.nextSibling : this.attrList.firstChild;
}
this.attrList.insertBefore(attr, before);
// Remove the old version of this attribute from the DOM.
let oldAttr = this.attrs[aAttr.name];
if (oldAttr && oldAttr.parentNode) {
oldAttr.parentNode.removeChild(oldAttr);
}
this.attrs[aAttr.name] = attr;
name.textContent = aAttr.name;
val.textContent = aAttr.value;

View File

@ -40,5 +40,7 @@
<div id="retag-me-2"></div>
</div>
<div id="node25"></div>
<div id="node26" style='background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'></div>
<div id="node27" class="Double &quot; and single &apos;"></div>
</body>
</html>

View File

@ -239,6 +239,79 @@ function test() {
});
}
},
{
desc: "Modify inline style containing \"",
before: function() {
assertAttributes(doc.querySelector("#node26"), {
id: "node26",
style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'
});
},
execute: function(after) {
inspector.once("markupmutation", after);
let editor = getContainerForRawNode(markup, doc.querySelector("#node26")).editor;
let attr = editor.attrs["style"].querySelector(".editable");
attr.focus();
EventUtils.sendKey("return", inspector.panelWin);
let input = inplaceEditor(attr).input;
let value = input.value;
is (value,
"style='background-image: url(\"moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F\");'",
"Value contains actual double quotes"
);
value = value.replace(/mozilla\.org/, "mozilla.com");
input.value = value;
EventUtils.sendKey("return", inspector.panelWin);
},
after: function() {
assertAttributes(doc.querySelector("#node26"), {
id: "node26",
style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.com%2F");'
});
}
},
{
desc: "Modify inline style containing \" and \'",
before: function() {
assertAttributes(doc.querySelector("#node27"), {
id: "node27",
class: 'Double " and single \''
});
},
execute: function(after) {
inspector.once("markupmutation", after);
let editor = getContainerForRawNode(markup, doc.querySelector("#node27")).editor;
let attr = editor.attrs["class"].querySelector(".editable");
attr.focus();
EventUtils.sendKey("return", inspector.panelWin);
let input = inplaceEditor(attr).input;
let value = input.value;
is (value, "class=\"Double &quot; and single '\"", "Value contains &quot;");
value = value.replace(/Double/, "&quot;").replace(/single/, "'");
input.value = value;
EventUtils.sendKey("return", inspector.panelWin);
},
after: function() {
assertAttributes(doc.querySelector("#node27"), {
id: "node27",
class: '" " and \' \''
});
}
},
{
desc: "Add an attribute value without closing \"",
enteredText: 'style="display: block;',

View File

@ -687,6 +687,7 @@ ResponsiveUI.prototype = {
this.stack.removeAttribute("notransition");
}
this.ignoreY = false;
this.ignoreX = false;
this.isResizing = false;
},

View File

@ -42,7 +42,7 @@ function simpleInherit(aInspector, aRuleView)
styleNode.parentNode.removeChild(styleNode);
emptyInherit();
}).then(null, console.error);
});
}
function emptyInherit()
@ -68,7 +68,7 @@ function emptyInherit()
styleNode.parentNode.removeChild(styleNode);
elementStyleInherit();
}).then(null, console.error);
});
}
function elementStyleInherit()
@ -92,7 +92,7 @@ function elementStyleInherit()
is(inheritProp.name, "color", "color should have been inherited.");
finishTest();
}).then(null, console.error);
});
}
function finishTest()

View File

@ -37,7 +37,7 @@ function SI_inspectNode()
is(span, computedView.viewedElement.rawNode(),
"style inspector node matches the selected node");
SI_toggleDefaultStyles();
}).then(null, (err) => console.error(err));
});
}
function SI_toggleDefaultStyles()

View File

@ -60,7 +60,7 @@
<!ENTITY btnPageJS.accesskey "J">
<!ENTITY btnPageSecurity.label "Security">
<!ENTITY btnPageSecurity.tooltip "Log security errors and warnings">
<!ENTITY btnPageSecurity.accesskey "S">
<!ENTITY btnPageSecurity.accesskey "u">
<!-- LOCALIZATION NOTE (btnPageLogging): This is used as the text of the
- the toolbar. It shows or hides messages that the web developer inserted on

View File

@ -9,12 +9,6 @@
.newattr {
cursor: pointer;
}
/* Give some padding to focusable elements to match the editor input
* that will replace them. */
span[tabindex] {
display: inline-block;
padding: 1px 0;
}

View File

@ -30,6 +30,7 @@ _MOZBUILD_EXTERNAL_VARIABLES := \
TEST_DIRS \
TIERS \
TOOL_DIRS \
XPCSHELL_TESTS \
XPIDL_MODULE \
$(NULL)

View File

@ -28,8 +28,11 @@ NoFilesSelected=No files selected.
# %S will be a number greater or equal to 2.
XFilesSelected=%S files selected.
ColorPicker=Choose a color
# LOCALIZATION NOTE (AndXMoreFiles): this string is shown at the end of the
# tooltip text for <input type='file' multiple> when there are more than 21
# files selected (when we will only list the first 20, plus an "and X more"
# line). %S will be the number of files minus 20.
AndXMoreFiles=and %S more
# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This string is shown at the end of the tooltip text for <input type='file'
# multiple> when there are more than 21 files selected (when we will only list
# the first 20, plus an "and X more" line). #1 represents the number of files
# minus 20 and will always be a number equal to or greater than 2. So the
# singular case will never be used.
AndNMoreFiles=and one more;and #1 more

View File

@ -30,6 +30,7 @@ _MOZBUILD_EXTERNAL_VARIABLES := \
TEST_DIRS \
TIERS \
TOOL_DIRS \
XPCSHELL_TESTS \
XPIDL_MODULE \
$(NULL)

View File

@ -31,7 +31,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import java.util.UUID;
import java.util.LinkedHashMap;
import java.util.ArrayList;
public class PageActionLayout extends LinearLayout implements GeckoEventListener,
View.OnClickListener,
@ -40,7 +40,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
private final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
private final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
private LinkedHashMap<String, PageAction> mPageActionList;
private ArrayList<PageAction> mPageActionList;
private GeckoPopupMenu mPageActionsMenu;
private Context mContext;
private LinearLayout mLayout;
@ -53,7 +53,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
mContext = context;
mLayout = this;
mPageActionList = new LinkedHashMap<String, PageAction>();
mPageActionList = new ArrayList<PageAction>();
setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
refreshPageActionIcons();
@ -91,6 +91,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
final String id = message.getString("id");
final String title = message.getString("title");
final String imageURL = message.optString("icon");
final boolean mImportant = message.getBoolean("important");
addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
@Override
@ -103,7 +104,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id));
return true;
}
});
}, mImportant);
} else if (event.equals("PageActions:Remove")) {
final String id = message.getString("id");
@ -114,14 +115,19 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
}
}
public void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners) {
final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners);
mPageActionList.put(id, pageAction);
public void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners, boolean mImportant) {
final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners, mImportant);
int insertAt = mPageActionList.size();
while(insertAt > 0 && mPageActionList.get(insertAt-1).isImportant()) {
insertAt--;
}
mPageActionList.add(insertAt, pageAction);
BitmapUtils.getDrawable(mContext, imageData, new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(final Drawable d) {
if (mPageActionList.containsKey(id)) {
if (mPageActionList.contains(pageAction)) {
pageAction.setDrawable(d);
refreshPageActionIcons();
}
@ -130,8 +136,13 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
}
public void removePageAction(String id) {
mPageActionList.remove(id);
refreshPageActionIcons();
for(int i = 0; i < mPageActionList.size(); i++) {
if (mPageActionList.get(i).getID().equals(id)) {
mPageActionList.remove(i);
refreshPageActionIcons();
return;
}
}
}
private ImageButton createImageButton() {
@ -150,7 +161,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
} else {
mPageActionList.get(buttonClickedId).onClick();
getPageActionWithId(buttonClickedId).onClick();
}
}
}
@ -162,7 +173,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
return true;
} else {
return mPageActionList.get(buttonClickedId).onLongClick();
return getPageActionWithId(buttonClickedId).onLongClick();
}
}
@ -230,18 +241,17 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
if (mPageActionList.size() > buttonIndex) {
// Return the pageactions starting from the end of the list for the number of visible pageactions.
return getPageActionAt((mPageActionList.size() - totalVisibleButtons) + buttonIndex);
return mPageActionList.get((mPageActionList.size() - totalVisibleButtons) + buttonIndex);
}
return null;
}
private PageAction getPageActionAt(int index) {
int count = 0;
for(PageAction pageAction : mPageActionList.values()) {
if (count == index) {
private PageAction getPageActionWithId(String id) {
for(int i = 0; i < mPageActionList.size(); i++) {
PageAction pageAction = mPageActionList.get(i);
if (pageAction.getID().equals(id)) {
return pageAction;
}
count++;
}
return null;
}
@ -253,25 +263,27 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
mPageActionsMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
for(PageAction pageAction : mPageActionList.values()) {
if (pageAction.key() == item.getItemId()) {
int id = item.getItemId();
for(int i = 0; i < mPageActionList.size(); i++) {
PageAction pageAction = mPageActionList.get(i);
if (pageAction.key() == id) {
pageAction.onClick();
return true;
}
}
return true;
return false;
}
});
}
Menu menu = mPageActionsMenu.getMenu();
menu.clear();
int count = 0;
for(PageAction pageAction : mPageActionList.values()) {
if (count < toShow) {
for(int i = 0; i < mPageActionList.size(); i++) {
if (i < toShow) {
PageAction pageAction = mPageActionList.get(i);
MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
item.setIcon(pageAction.getDrawable());
}
count++;
}
mPageActionsMenu.show();
}
@ -287,12 +299,18 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
private String mTitle;
private String mId;
private int key;
private boolean mImportant;
public PageAction(String id, String title, Drawable image, OnPageActionClickListeners mOnPageActionClickListeners) {
public PageAction(String id,
String title,
Drawable image,
OnPageActionClickListeners mOnPageActionClickListeners,
boolean mImportant) {
this.mId = id;
this.mTitle = title;
this.mDrawable = image;
this.mOnPageActionClickListeners = mOnPageActionClickListeners;
this.mImportant = mImportant;
this.key = UUID.fromString(mId.subSequence(1, mId.length() - 2).toString()).hashCode();
}
@ -317,6 +335,10 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
return key;
}
public boolean isImportant() {
return mImportant;
}
public void onClick() {
if (mOnPageActionClickListeners != null) {
mOnPageActionClickListeners.onClick(mId);

View File

@ -74,13 +74,17 @@ var SelectionHandler = {
this.copySelection();
else
this._closeSelection();
} else if (this._activeType == this.TYPE_CURSOR) {
// attachCaret() is called in the "Gesture:SingleTap" handler in BrowserEventHandler
// We're guaranteed to call this first, because this observer was added last
this._closeSelection();
}
break;
}
case "Tab:Selected":
this._closeSelection();
break;
case "Window:Resize": {
if (this._activeType == this.TYPE_SELECTION) {
// Knowing when the page is done drawing is hard, so let's just cancel

View File

@ -1628,7 +1628,8 @@ var NativeWindow = {
type: "PageActions:Add",
id: id,
title: aOptions.title,
icon: resolveGeckoURI(aOptions.icon)
icon: resolveGeckoURI(aOptions.icon),
important: "important" in aOptions ? aOptions.important : false
});
this._items[id] = {
clickCallback: aOptions.clickCallback,
@ -7157,14 +7158,16 @@ let Reader = {
this.pageAction.id = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("readerMode.exit"),
icon: "drawable://reader_active",
clickCallback: this.pageAction.readerModeCallback
clickCallback: this.pageAction.readerModeCallback,
important: true
});
} else if (tab.readerEnabled) {
this.pageAction.id = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("readerMode.enter"),
icon: "drawable://reader",
clickCallback:this.pageAction.readerModeCallback,
longClickCallback: this.pageAction.readerModeActiveCallback
longClickCallback: this.pageAction.readerModeActiveCallback,
important: true
});
}
},

View File

@ -346,7 +346,7 @@ add_test(function test_bso_if_unmodified_since() {
let coll = server.user("123").collection("test");
do_check_neq(coll, null);
let time = coll.timestamp;
let time = coll.bso("bso").modified;
_("Ensure we get a 412 for specified times older than server time.");
let request = localRequest(server, "/2.0/123/storage/test/bso",

View File

@ -3366,6 +3366,18 @@
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPESANDPROPERTIES_MS": {
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPESANDPROPERTIES_MS": {
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_PROPERTY_MS": {
"kind": "exponential",
"high": "10000",

View File

@ -590,11 +590,11 @@
if (files.length == TRUNCATED_FILE_COUNT + 1) {
titleText += "\n" + files[TRUNCATED_FILE_COUNT].name;
} else if (files.length > TRUNCATED_FILE_COUNT + 1) {
let xmoreStr = bundle.GetStringFromName("AndXMoreFiles");
let xmoreStr = bundle.GetStringFromName("AndNMoreFiles");
let xmoreNum = files.length - TRUNCATED_FILE_COUNT;
let tmp = {};
Components.utils.import("resource://gre/modules/PluralForm.jsm", tmp);
let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("%S", xmoreNum);
let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("#1", xmoreNum);
titleText += "\n" + andXMoreStr;
}
}

View File

@ -6,12 +6,26 @@
/* General utilities used throughout devtools. */
/* Turn the error e into a string, without fail. */
/**
* Turn the error |aError| into a string, without fail.
*/
this.safeErrorString = function safeErrorString(aError) {
try {
var s = aError.toString();
if (typeof s === "string")
return s;
let errorString = aError.toString();
if (typeof errorString === "string") {
// Attempt to attach a stack to |errorString|. If it throws an error, or
// isn't a string, don't use it.
try {
if (aError.stack) {
let stack = aError.stack.toString();
if (typeof stack === "string") {
errorString += "\nStack: " + stack;
}
}
} catch (ee) { }
return errorString;
}
} catch (ee) { }
return "<failed trying to find error description>";
@ -22,9 +36,6 @@ this.safeErrorString = function safeErrorString(aError) {
*/
this.reportException = function reportException(aWho, aException) {
let msg = aWho + " threw an exception: " + safeErrorString(aException);
if (aException.stack) {
msg += "\nCall stack:\n" + aException.stack;
}
dump(msg + "\n");

View File

@ -1,15 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
XPCSHELL_TESTS = unit
include $(topsrcdir)/config/rules.mk

View File

@ -5,3 +5,5 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MODULE = 'test_webapps_actor'
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']

View File

@ -3,4 +3,8 @@ head = head_apps.js
tail = tail_apps.js
[test_webappsActor.js]
# Persistent failures.
skip-if = true
[test_appInstall.js]
# Persistent failures.
skip-if = true

View File

@ -1626,8 +1626,22 @@ ThreadClient.prototype = {
return this._threadGrips[aForm.actor] = new SourceClient(this._client,
aForm);
}
},
/**
* Request the prototype and own properties of mutlipleObjects.
*
* @param aOnResponse function
* Called with the request's response.
* @param actors [string]
* List of actor ID of the queried objects.
*/
getPrototypesAndProperties: DebuggerClient.requester({
type: "prototypesAndProperties",
actors: args(0)
}, {
telemetry: "PROTOTYPESANDPROPERTIES"
})
};
eventSource(ThreadClient.prototype);

View File

@ -4,6 +4,8 @@
# 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/.
TEST_DIRS += ['tests']
PARALLEL_DIRS += [
'server',
'client',

View File

@ -2070,6 +2070,34 @@ ThreadActor.prototype = {
return true;
},
/**
* Get prototypes and properties of multiple objects.
*/
onPrototypesAndProperties: function TA_onPrototypesAndProperties(aRequest) {
let result = {};
for (let actorID of aRequest.actors) {
// This code assumes that there are no lazily loaded actors returned
// by this call.
let actor = this.conn.getActor(actorID);
if (!actor) {
return { from: this.actorID,
error: "noSuchActor" };
}
let handler = actor.onPrototypeAndProperties;
if (!handler) {
return { from: this.actorID,
error: "unrecognizedPacketType",
message: ('Actor "' + actorID +
'" does not recognize the packet type ' +
'"prototypeAndProperties"') };
}
result[actorID] = handler.call(actor, {});
}
return { from: this.actorID,
actors: result };
}
};
ThreadActor.prototype.requestTypes = {
@ -2084,7 +2112,8 @@ ThreadActor.prototype.requestTypes = {
"releaseMany": ThreadActor.prototype.onReleaseMany,
"setBreakpoint": ThreadActor.prototype.onSetBreakpoint,
"sources": ThreadActor.prototype.onSources,
"threadGrips": ThreadActor.prototype.onThreadGrips
"threadGrips": ThreadActor.prototype.onThreadGrips,
"prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
};

View File

@ -535,7 +535,7 @@ function timeSinceTraceStarted({ startTime }) {
* The type of completion value to serialize (return, throw, or yield).
*/
function serializeCompletionValue(aType, { value }) {
if (typeof value[aType] === "undefined") {
if (!Object.hasOwnProperty.call(value, aType)) {
return undefined;
}
return createValueGrip(value[aType], true);

View File

@ -834,13 +834,12 @@ DebuggerServerConnection.prototype = {
},
_unknownError: function DSC__unknownError(aPrefix, aError) {
let errorString = safeErrorString(aError);
errorString += "\n" + aError.stack;
let errorString = aPrefix + ": " + safeErrorString(aError);
Cu.reportError(errorString);
dumpn(errorString);
return {
error: "unknownError",
message: (aPrefix + "': " + errorString)
message: errorString
};
},

View File

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This tests exercises getProtypesAndProperties message accepted
* by a thread actor.
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
gDebuggee.eval(function stopMe(arg1, arg2) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
{
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
let args = aPacket.frame.arguments;
gThreadClient.getPrototypesAndProperties([args[0].actor, args[1].actor], function(aResponse) {
let obj1 = aResponse.actors[args[0].actor];
let obj2 = aResponse.actors[args[1].actor];
do_check_eq(obj1.ownProperties.x.configurable, true);
do_check_eq(obj1.ownProperties.x.enumerable, true);
do_check_eq(obj1.ownProperties.x.writable, true);
do_check_eq(obj1.ownProperties.x.value, 10);
do_check_eq(obj1.ownProperties.y.configurable, true);
do_check_eq(obj1.ownProperties.y.enumerable, true);
do_check_eq(obj1.ownProperties.y.writable, true);
do_check_eq(obj1.ownProperties.y.value, "kaiju");
do_check_eq(obj2.ownProperties.z.configurable, true);
do_check_eq(obj2.ownProperties.z.enumerable, true);
do_check_eq(obj2.ownProperties.z.writable, true);
do_check_eq(obj2.ownProperties.z.value, 123);
do_check_true(obj1.prototype != undefined);
do_check_true(obj2.prototype != undefined);
gThreadClient.resume(function() {
finishClient(gClient);
});
});
});
gDebuggee.eval("stopMe({ x: 10, y: 'kaiju'}, { z: 123 })");
}

View File

@ -2,8 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that objects are correctly serialized and sent in exitedFrame
* packets.
* Tests that values are correctly serialized and sent in enteredFrame
* and exitedFrame packets.
*/
let {defer} = devtools.require("sdk/core/promise");
@ -30,73 +30,8 @@ function run_test()
function test_enter_exit_frame()
{
gTraceClient.addListener("exitedFrame", function(aEvent, aPacket) {
if (aPacket.sequence === 3) {
let obj = aPacket.return;
do_check_eq(typeof obj, "object",
'exitedFrame response should have return value');
do_check_eq(typeof obj.prototype, "object",
'return value should have prototype');
do_check_eq(typeof obj.ownProperties, "object",
'return value should have ownProperties list');
do_check_eq(typeof obj.safeGetterValues, "object",
'return value should have safeGetterValues');
do_check_eq(typeof obj.ownProperties.num, "object",
'return value should have property "num"');
do_check_eq(typeof obj.ownProperties.str, "object",
'return value should have property "str"');
do_check_eq(typeof obj.ownProperties.bool, "object",
'return value should have property "bool"');
do_check_eq(typeof obj.ownProperties.undef, "object",
'return value should have property "undef"');
do_check_eq(typeof obj.ownProperties.undef.value, "object",
'return value property "undef" should be a grip');
do_check_eq(typeof obj.ownProperties.nil, "object",
'return value should have property "nil"');
do_check_eq(typeof obj.ownProperties.nil.value, "object",
'return value property "nil" should be a grip');
do_check_eq(typeof obj.ownProperties.obj, "object",
'return value should have property "obj"');
do_check_eq(typeof obj.ownProperties.obj.value, "object",
'return value property "obj" should be a grip');
do_check_eq(typeof obj.ownProperties.arr, "object",
'return value should have property "arr"');
do_check_eq(typeof obj.ownProperties.arr.value, "object",
'return value property "arr" should be a grip');
do_check_eq(typeof obj.ownProperties.inf, "object",
'return value should have property "inf"');
do_check_eq(typeof obj.ownProperties.inf.value, "object",
'return value property "inf" should be a grip');
do_check_eq(typeof obj.ownProperties.ninf, "object",
'return value should have property "ninf"');
do_check_eq(typeof obj.ownProperties.ninf.value, "object",
'return value property "ninf" should be a grip');
do_check_eq(typeof obj.ownProperties.nan, "object",
'return value should have property "nan"');
do_check_eq(typeof obj.ownProperties.nan.value, "object",
'return value property "nan" should be a grip');
do_check_eq(typeof obj.ownProperties.nzero, "object",
'return value should have property "nzero"');
do_check_eq(typeof obj.ownProperties.nzero.value, "object",
'return value property "nzero" should be a grip');
do_check_eq(obj.prototype.type, "object");
do_check_eq(obj.ownProperties.num.value, 25);
do_check_eq(obj.ownProperties.str.value, "foo");
do_check_eq(obj.ownProperties.bool.value, false);
do_check_eq(obj.ownProperties.undef.value.type, "undefined");
do_check_eq(obj.ownProperties.nil.value.type, "null");
do_check_eq(obj.ownProperties.obj.value.type, "object");
do_check_eq(obj.ownProperties.obj.value.class, "Object");
do_check_eq(obj.ownProperties.arr.value.type, "object");
do_check_eq(obj.ownProperties.arr.value.class, "Array");
do_check_eq(obj.ownProperties.inf.value.type, "Infinity");
do_check_eq(obj.ownProperties.ninf.value.type, "-Infinity");
do_check_eq(obj.ownProperties.nan.value.type, "NaN");
do_check_eq(obj.ownProperties.nzero.value.type, "-0");
}
});
gTraceClient.addListener("enteredFrame", check_packet);
gTraceClient.addListener("exitedFrame", check_packet);
start_trace()
.then(eval_code)
@ -109,32 +44,45 @@ function test_enter_exit_frame()
function start_trace()
{
let deferred = defer();
gTraceClient.startTrace(["return"], null, function() { deferred.resolve(); });
gTraceClient.startTrace(["arguments", "return"], null, function() { deferred.resolve(); });
return deferred.promise;
}
function eval_code()
{
gDebuggee.eval("(" + function() {
function foo() {
let obj = {};
obj.self = obj;
return {
num: 25,
str: "foo",
bool: false,
undef: undefined,
nil: null,
obj: obj,
arr: [1,2,3,4,5],
inf: Infinity,
ninf: -Infinity,
nan: NaN,
nzero: -0
};
function identity(x) {
return x;
}
foo();
let circular = {};
circular.self = circular;
let obj = {
num: 0,
str: "foo",
bool: false,
undef: undefined,
nil: null,
inf: Infinity,
ninf: -Infinity,
nan: NaN,
nzero: -0,
obj: circular,
arr: [1,2,3,4,5]
};
identity();
identity(0);
identity("");
identity(false);
identity(undefined);
identity(null);
identity(Infinity);
identity(-Infinity);
identity(NaN);
identity(-0);
identity(obj);
} + ")()");
}
@ -144,3 +92,132 @@ function stop_trace()
gTraceClient.stopTrace(null, function() { deferred.resolve(); });
return deferred.promise;
}
function check_packet(aEvent, aPacket)
{
let value = (aPacket.type === "enteredFrame" && aPacket.arguments)
? aPacket.arguments[0]
: aPacket.return;
switch(aPacket.sequence) {
case 2:
do_check_eq(typeof aPacket.arguments, "object",
"zero-argument function call should send arguments list");
do_check_eq(aPacket.arguments.length, 0,
"zero-argument function call should send zero-length arguments list");
break;
case 3:
check_value(value, "object", "undefined");
break;
case 4:
case 5:
check_value(value, "number", 0);
break;
case 6:
case 7:
check_value(value, "string", "");
break;
case 8:
case 9:
check_value(value, "boolean", false);
break;
case 10:
case 11:
check_value(value, "object", "undefined");
break;
case 12:
case 13:
check_value(value, "object", "null");
break;
case 14:
case 15:
check_value(value, "object", "Infinity");
break;
case 16:
case 17:
check_value(value, "object", "-Infinity");
break;
case 18:
case 19:
check_value(value, "object", "NaN");
break;
case 20:
case 21:
check_value(value, "object", "-0");
break;
case 22:
case 23:
check_object(aPacket.type, value);
break;
}
}
function check_value(aActual, aExpectedType, aExpectedValue)
{
do_check_eq(typeof aActual, aExpectedType);
do_check_eq(aExpectedType === "object" ? aActual.type : aActual, aExpectedValue);
}
function check_object(aType, aObj) {
do_check_eq(typeof aObj, "object",
'serialized object should be present in packet');
do_check_eq(typeof aObj.prototype, "object",
'serialized object should have prototype');
do_check_eq(typeof aObj.ownProperties, "object",
'serialized object should have ownProperties list');
do_check_eq(typeof aObj.safeGetterValues, "object",
'serialized object should have safeGetterValues');
do_check_eq(typeof aObj.ownProperties.num, "object",
'serialized object should have property "num"');
do_check_eq(typeof aObj.ownProperties.str, "object",
'serialized object should have property "str"');
do_check_eq(typeof aObj.ownProperties.bool, "object",
'serialized object should have property "bool"');
do_check_eq(typeof aObj.ownProperties.undef, "object",
'serialized object should have property "undef"');
do_check_eq(typeof aObj.ownProperties.undef.value, "object",
'serialized object property "undef" should be a grip');
do_check_eq(typeof aObj.ownProperties.nil, "object",
'serialized object should have property "nil"');
do_check_eq(typeof aObj.ownProperties.nil.value, "object",
'serialized object property "nil" should be a grip');
do_check_eq(typeof aObj.ownProperties.obj, "object",
'serialized object should have property "aObj"');
do_check_eq(typeof aObj.ownProperties.obj.value, "object",
'serialized object property "aObj" should be a grip');
do_check_eq(typeof aObj.ownProperties.arr, "object",
'serialized object should have property "arr"');
do_check_eq(typeof aObj.ownProperties.arr.value, "object",
'serialized object property "arr" should be a grip');
do_check_eq(typeof aObj.ownProperties.inf, "object",
'serialized object should have property "inf"');
do_check_eq(typeof aObj.ownProperties.inf.value, "object",
'serialized object property "inf" should be a grip');
do_check_eq(typeof aObj.ownProperties.ninf, "object",
'serialized object should have property "ninf"');
do_check_eq(typeof aObj.ownProperties.ninf.value, "object",
'serialized object property "ninf" should be a grip');
do_check_eq(typeof aObj.ownProperties.nan, "object",
'serialized object should have property "nan"');
do_check_eq(typeof aObj.ownProperties.nan.value, "object",
'serialized object property "nan" should be a grip');
do_check_eq(typeof aObj.ownProperties.nzero, "object",
'serialized object should have property "nzero"');
do_check_eq(typeof aObj.ownProperties.nzero.value, "object",
'serialized object property "nzero" should be a grip');
do_check_eq(aObj.prototype.type, "object");
do_check_eq(aObj.ownProperties.num.value, 0);
do_check_eq(aObj.ownProperties.str.value, "foo");
do_check_eq(aObj.ownProperties.bool.value, false);
do_check_eq(aObj.ownProperties.undef.value.type, "undefined");
do_check_eq(aObj.ownProperties.nil.value.type, "null");
do_check_eq(aObj.ownProperties.obj.value.type, "object");
do_check_eq(aObj.ownProperties.obj.value.class, "Object");
do_check_eq(aObj.ownProperties.arr.value.type, "object");
do_check_eq(aObj.ownProperties.arr.value.class, "Array");
do_check_eq(aObj.ownProperties.inf.value.type, "Infinity");
do_check_eq(aObj.ownProperties.ninf.value.type, "-Infinity");
do_check_eq(aObj.ownProperties.nan.value.type, "NaN");
do_check_eq(aObj.ownProperties.nzero.value.type, "-0");
}

View File

@ -127,6 +127,7 @@ reason = bug 820380
[test_objectgrips-06.js]
[test_objectgrips-07.js]
[test_objectgrips-08.js]
[test_objectgrips-09.js]
[test_interrupt.js]
[test_stepping-01.js]
[test_stepping-02.js]

View File

@ -0,0 +1,9 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
MODULE = 'test_devtools'
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']

View File

@ -0,0 +1,42 @@
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
// Register a console listener, so console messages don't just disappear
// into the ether.
let errorCount = 0;
let listener = {
observe: function (aMessage) {
errorCount++;
try {
// If we've been given an nsIScriptError, then we can print out
// something nicely formatted, for tools like Emacs to pick up.
var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
scriptErrorFlagsToKind(aMessage.flags) + ": " +
aMessage.errorMessage + "\n");
var string = aMessage.errorMessage;
} catch (x) {
// Be a little paranoid with message, as the whole goal here is to lose
// no information.
try {
var string = "" + aMessage.message;
} catch (x) {
var string = "<error converting error message to string>";
}
}
// Make sure we exit all nested event loops so that the test can finish.
while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
DebuggerServer.xpcInspector.exitNestedEventLoop();
}
do_throw("head_dbg.js got console message: " + string + "\n");
}
};
let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
consoleService.registerListener(listener);

View File

@ -0,0 +1,54 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test DevToolsUtils.safeErrorString
function run_test() {
test_with_error();
test_with_tricky_error();
test_with_string();
test_with_thrower();
test_with_psychotic();
}
function test_with_error() {
let s = DevToolsUtils.safeErrorString(new Error("foo bar"));
// Got the message.
do_check_true(s.contains("foo bar"));
// Got the stack.
do_check_true(s.contains("test_with_error"))
do_check_true(s.contains("test_safeErrorString.js"));
}
function test_with_tricky_error() {
let e = new Error("batman");
e.stack = { toString: Object.create(null) };
let s = DevToolsUtils.safeErrorString(e);
// Still got the message, despite a bad stack property.
do_check_true(s.contains("batman"));
}
function test_with_string() {
let s = DevToolsUtils.safeErrorString("not really an error");
// Still get the message.
do_check_true(s.contains("not really an error"));
}
function test_with_thrower() {
let s = DevToolsUtils.safeErrorString({
toString: () => {
throw new Error("Muahahaha");
}
});
// Still don't fail, get string back.
do_check_eq(typeof s, "string");
}
function test_with_psychotic() {
let s = DevToolsUtils.safeErrorString({
toString: () => Object.create(null)
});
// Still get a string out, and no exceptions thrown
do_check_eq(typeof s, "string");
}

View File

@ -0,0 +1,5 @@
[DEFAULT]
head = head_devtools.js
tail =
[test_safeErrorString.js]

View File

@ -2207,6 +2207,11 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
if (mSaver) {
mSaver->Finish(aReason);
mSaver = nullptr;
} else if (mStopRequestIssued && mTempFile) {
// This branch can only happen when the user cancels the helper app dialog
// when the request has completed. The temp file has to be removed here,
// because mSaver has been released at that time with the temp file left.
(void)mTempFile->Remove(false);
}
// Break our reference cycle with the helper app dialog (set up in

View File

@ -6,6 +6,8 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const UNKNOWN_FAIL = ["geolocation", "desktop-notification"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://webapprt/modules/WebappRT.jsm");
@ -16,64 +18,83 @@ ContentPermission.prototype = {
classID: Components.ID("{07ef5b2e-88fb-47bd-8cec-d3b0bef11ac4}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
_getChromeWindow: function(aWindow) {
return aWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
},
prompt: function(request) {
// Only handle geolocation requests for now
if (request.type != "geolocation") {
return;
// Reuse any remembered permission preferences
let result =
Services.perms.testExactPermissionFromPrincipal(request.principal,
request.type);
// We used to use the name "geo" for the geolocation permission, now we're
// using "geolocation". We need to check both to support existing
// installations.
if ((result == Ci.nsIPermissionManager.UNKNOWN_ACTION ||
result == Ci.nsIPermissionManager.PROMPT_ACTION) &&
request.type == "geolocation") {
let geoResult = Services.perms.testExactPermission(request.principal.URI,
"geo");
// We override the result only if the "geo" permission was allowed or
// denied.
if (geoResult == Ci.nsIPermissionManager.ALLOW_ACTION ||
geoResult == Ci.nsIPermissionManager.DENY_ACTION) {
result = geoResult;
}
}
// Reuse any remembered permission preferences
let result = Services.perms.testExactPermissionFromPrincipal(request.principal, "geo");
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
request.allow();
return;
}
else if (result == Ci.nsIPermissionManager.DENY_ACTION) {
} else if (result == Ci.nsIPermissionManager.DENY_ACTION ||
(result == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
UNKNOWN_FAIL.indexOf(request.type) >= 0)) {
request.cancel();
return;
}
function getChromeWindow(aWindow) {
var chromeWin = aWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
return chromeWin;
}
// Display a prompt at the top level
let {name} = WebappRT.config.app.manifest;
let requestingWindow = request.window.top;
let chromeWin = getChromeWindow(requestingWindow);
let chromeWin = this._getChromeWindow(requestingWindow);
let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
// Construct a prompt with share/don't and remember checkbox
let remember = {value: false};
let choice = Services.prompt.confirmEx(
chromeWin,
bundle.formatStringFromName("geolocation.title", [name], 1),
bundle.GetStringFromName("geolocation.description"),
bundle.formatStringFromName(request.type + ".title", [name], 1),
bundle.GetStringFromName(request.type + ".description"),
// Set both buttons to strings with the cancel button being default
Ci.nsIPromptService.BUTTON_POS_1_DEFAULT |
Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_0 |
Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_1,
bundle.GetStringFromName("geolocation.sharelocation"),
bundle.GetStringFromName("geolocation.dontshare"),
bundle.GetStringFromName(request.type + ".allow"),
bundle.GetStringFromName(request.type + ".deny"),
null,
bundle.GetStringFromName("geolocation.remember"),
bundle.GetStringFromName(request.type + ".remember"),
remember);
// Persist the choice if the user wants to remember
let action = Ci.nsIPermissionManager.ALLOW_ACTION;
if (choice != 0) {
action = Ci.nsIPermissionManager.DENY_ACTION;
}
if (remember.value) {
let action = Ci.nsIPermissionManager.ALLOW_ACTION;
if (choice != 0) {
action = Ci.nsIPermissionManager.DENY_ACTION;
}
Services.perms.addFromPrincipal(request.principal, "geo", action);
// Persist the choice if the user wants to remember
Services.perms.addFromPrincipal(request.principal, request.type, action);
} else {
// Otherwise allow the permission for the current session
Services.perms.addFromPrincipal(request.principal, request.type, action,
Ci.nsIPermissionManager.EXPIRE_SESSION);
}
// Trigger the selected choice

View File

@ -45,7 +45,7 @@
<key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
<key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
<key id="key_quitApplication"
key="&quitApplicationCmdMac.key;"
key="&quitApplicationCmdUnix.key;"
command="cmd_quitApplication"
modifiers="accel"/>
<key id="key_hideThisAppCmdMac"

View File

@ -18,12 +18,14 @@
<!-- On Mac, we create the Quit and Hide command labels dynamically,
- using properties in window.properties, in order to include the name
- of the webapp in the labels without creating a DTD file for it. -->
<!ENTITY quitApplicationCmdMac.key "Q">
<!ENTITY hideThisAppCmdMac.key "H">
<!ENTITY hideOtherAppsCmdMac.label "Hide Others">
<!ENTITY hideOtherAppsCmdMac.key "H">
<!ENTITY showAllAppsCmdMac.label "Show All">
<!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX -->
<!ENTITY quitApplicationCmdUnix.key "Q">
<!ENTITY editMenu.label "Edit">
<!ENTITY editMenu.accesskey "E">
<!ENTITY undoCmd.label "Undo">

View File

@ -20,10 +20,18 @@ hideApplicationCmdMac.label=Hide %S
# the webapp.
geolocation.title=%S - Share Location
geolocation.description=Do you want to share your location?
geolocation.sharelocation=Share Location
geolocation.dontshare=Don't Share
geolocation.allow=Share Location
geolocation.deny=Don't Share
geolocation.remember=Remember my choice
# LOCALIZATION NOTE (desktop-notification.title): %S will be replaced with the
# name of the webapp.
desktop-notification.title=%S - Show notifications
desktop-notification.description=Do you want to allow notifications?
desktop-notification.allow=Show
desktop-notification.deny=Don't show
desktop-notification.remember=Remember my choice
# LOCALIZATION NOTE (webapps.install.title): %S will be replaced with the name
# of the webapp being installed.
webapps.install.title=Install %S