Merge f-t to m-c

This commit is contained in:
Phil Ringnalda 2014-03-16 08:36:21 -07:00
commit 6d0fa5954e
5 changed files with 315 additions and 175 deletions

View File

@ -23,15 +23,20 @@ function test() {
Task.spawn(function* () {
try {
yield ensureSourceIs(gPanel, CODE_URL, true);
// Refresh and hit the debugger statement before the location we want to
// set our breakpoints. We have to pause before the breakpoint locations
// so that GC doesn't get a chance to kick in and collect the IIFE's
// script, which would causes us to receive a 'noScript' error from the
// server when we try to set the breakpoints.
const [paused, ] = yield promise.all([
waitForThreadEvents(gPanel, "paused"),
reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
]);
// Pause and set our breakpoints.
yield doInterrupt();
is(paused.why.type, "debuggerStatement");
// Set our breakpoints.
const [bp1, bp2, bp3] = yield promise.all([
setBreakpoint({
url: CODE_URL,
line: 2
}),
setBreakpoint({
url: CODE_URL,
line: 3
@ -39,23 +44,31 @@ function test() {
setBreakpoint({
url: CODE_URL,
line: 4
}),
setBreakpoint({
url: CODE_URL,
line: 5
})
]);
// Should hit the first breakpoint on reload.
// Refresh and hit the debugger statement again.
yield promise.all([
reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
waitForCaretUpdated(gPanel, 2)
waitForCaretAndScopes(gPanel, 1)
]);
// And should hit the other breakpoints as we resume.
// And we should hit the breakpoints as we resume.
yield promise.all([
doResume(),
waitForCaretUpdated(gPanel, 3)
waitForCaretAndScopes(gPanel, 3)
]);
yield promise.all([
doResume(),
waitForCaretUpdated(gPanel, 4)
waitForCaretAndScopes(gPanel, 4)
]);
yield promise.all([
doResume(),
waitForCaretAndScopes(gPanel, 5)
]);
// Clean up the breakpoints.

View File

@ -1,3 +1,4 @@
debugger;
var a = (function(){
var b = 9;
console.log("x", b);

View File

@ -811,9 +811,9 @@ this.UITour = {
let minDimension = Math.min(highlightHeight, highlightWidth);
let maxDimension = Math.max(highlightHeight, highlightWidth);
// If the dimensions are within 110% of each other (to include the bookmarks button),
// If the dimensions are within 200% of each other (to include the bookmarks button),
// make the highlight a circle with the largest dimension as the diameter.
if (maxDimension / minDimension <= 2.1) {
if (maxDimension / minDimension <= 3.0) {
highlightHeight = highlightWidth = maxDimension;
highlighter.style.borderRadius = "100%";
} else {

View File

@ -1918,8 +1918,7 @@ var NativeWindow = {
},
contextmenus: {
items: {}, // a list of context menu items that we may show
_nativeItemsSeparator: 0, // the index to insert native context menu items at
_contextId: 0, // id to assign to new context menu items if they are added
DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items
init: function() {
Services.obs.addObserver(this, "Gesture:LongPress", false);
@ -1929,27 +1928,26 @@ var NativeWindow = {
Services.obs.removeObserver(this, "Gesture:LongPress");
},
add: function(aName, aSelector, aCallback) {
if (!aName)
add: function() {
let args;
if (arguments.length == 1) {
args = arguments[0];
} else if (arguments.length == 3) {
args = {
label : arguments[0],
selector: arguments[1],
callback: arguments[2]
};
} else {
throw "Incorrect number of parameters";
}
if (!args.label)
throw "Menu items must have a name";
let item = {
name: aName,
context: aSelector,
callback: aCallback,
matches: function(aElt, aX, aY) {
return this.context.matches(aElt, aX, aY);
},
getValue: function(aElt) {
return {
label: (typeof this.name == "function") ? this.name(aElt) : this.name,
id: this.id
}
}
};
item.id = this._contextId++;
this.items[item.id] = item;
return item.id;
let cmItem = new ContextMenuItem(args);
this.items[cmItem.id] = cmItem;
return cmItem.id;
},
remove: function(aId) {
@ -2114,123 +2112,87 @@ var NativeWindow = {
else this._targetRef = null;
},
_addHTMLContextMenuItems: function cm_addContextMenuItems(aMenu, aParent) {
for (let i = 0; i < aMenu.childNodes.length; i++) {
let item = aMenu.childNodes[i];
if (!item.label)
_addHTMLContextMenuItemsForElement: function(element) {
let htmlMenu = element.contextMenu;
if (!htmlMenu)
return;
htmlMenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
htmlMenu.sendShowEvent();
this._addHTMLContextMenuItemsForMenu(htmlMenu, element);
},
_addHTMLContextMenuItemsForMenu: function(menu, target) {
for (let i = 0; i < menu.childNodes.length; i++) {
let elt = menu.childNodes[i];
if (!elt.label)
continue;
let id = this._contextId++;
let menuitem = {
id: id,
isGroup: false,
callback: (function(aTarget, aX, aY) {
// If this is a menu item, show a new context menu with the submenu in it
if (item instanceof Ci.nsIDOMHTMLMenuElement) {
this.menuitems = [];
this._nativeItemsSeparator = 0;
this._addHTMLContextMenuItems(item, id);
this._innerShow(aTarget, aX, aY);
} else {
// oltherwise just click the item
item.click();
}
}).bind(this),
getValue: function(aElt) {
if (item.hasAttribute("hidden"))
return null;
return {
icon: item.icon,
label: item.label,
id: id,
disabled: item.disabled,
parent: item instanceof Ci.nsIDOMHTMLMenuElement
}
}
};
this.menuitems.splice(this._nativeItemsSeparator, 0, menuitem);
this._nativeItemsSeparator++;
this.menuitems.push(new HTMLContextMenuItem(elt, target));
}
},
_getMenuItemForId: function(aId) {
_containsItem: function(aId) {
if (!this.menuitems)
return null;
for (let i = 0; i < this.menuitems.length; i++) {
if (this.menuitems[i].id == aId)
return this.menuitems[i];
let menu = this.menuitems;
for (let i = 0; i < menu.length; i++) {
if (menu[i].id == aId)
return menu[i];
}
return null;
},
shouldShow: function() {
return this.menuitems.length > 0;
},
_addNativeContextMenuItems: function(element, x, y) {
for (let itemId of Object.keys(this.items)) {
let item = this.items[itemId];
if (!this._containsItem(item.id) && item.matches(element, x, y)) {
this.menuitems.push(item);
}
}
},
// Checks if there are context menu items to show, and if it finds them
// sends a contextmenu event to content. We also send showing events to
// any html5 context menus we are about to show
_sendToContent: function(aX, aY) {
// find and store the top most element this context menu is being shown for
// use the highlighted element if possible, otherwise look for nearby clickable elements
// If we still don't find one we fall back to using anything
let target = BrowserEventHandler._highlightElement || ElementTouchHelper.elementFromPoint(aX, aY);
_sendToContent: function(x, y) {
let target = BrowserEventHandler._highlightElement || ElementTouchHelper.elementFromPoint(x, y);
if (!target)
target = ElementTouchHelper.anyElementFromPoint(aX, aY);
target = ElementTouchHelper.anyElementFromPoint(x, y);
if (!target)
return;
// store a weakref to the target to be used when the context menu event returns
this._target = target;
this.menuitems = [];
let menuitemsSet = false;
Services.obs.notifyObservers(null, "before-build-contextmenu", "");
// now walk up the tree and for each node look for any context menu items that apply
let element = target;
this._nativeItemsSeparator = 0;
while (element) {
// first check for any html5 context menus that might exist
let contextmenu = element.contextMenu;
if (contextmenu) {
// send this before we build the list to make sure the site can update the menu
contextmenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
contextmenu.sendShowEvent();
this._addHTMLContextMenuItems(contextmenu, null);
}
// then check for any context menu items registered in the ui
for (let itemId of Object.keys(this.items)) {
let item = this.items[itemId];
if (!this._getMenuItemForId(item.id) && item.matches(element, aX, aY)) {
this.menuitems.push(item);
}
}
element = element.parentNode;
}
this._buildMenu(x, y);
// only send the contextmenu event to content if we are planning to show a context menu (i.e. not on every long tap)
if (this.menuitems.length > 0) {
if (this.shouldShow()) {
let event = target.ownerDocument.createEvent("MouseEvent");
event.initMouseEvent("contextmenu", true, true, content,
0, aX, aY, aX, aY, false, false, false, false,
event.initMouseEvent("contextmenu", true, true, target.defaultView,
0, x, y, x, y, false, false, false, false,
0, null);
target.ownerDocument.defaultView.addEventListener("contextmenu", this, false);
target.dispatchEvent(event);
} else {
this._target = null;
BrowserEventHandler._cancelTapHighlight();
this.menuitems = null;
Services.obs.notifyObservers({target: target, x: x, y: y}, "context-menu-not-shown", "");
if (SelectionHandler.canSelect(target)) {
if (!SelectionHandler.startSelection(target, {
mode: SelectionHandler.SELECT_AT_POINT,
x: aX,
y: aY
x: x,
y: y
})) {
SelectionHandler.attachCaret(target);
}
@ -2238,6 +2200,41 @@ var NativeWindow = {
}
},
_getTitle: function(node) {
if (node.hasAttribute && node.hasAttribute("title")) {
return node.getAttribute("title");
}
return this._getUrl(node);
},
_getUrl: function(node) {
if ((node instanceof Ci.nsIDOMHTMLAnchorElement && node.href) ||
(node instanceof Ci.nsIDOMHTMLAreaElement && node.href)) {
return this._getLinkURL(node);
} else if (node instanceof Ci.nsIImageLoadingContent && node.currentURI) {
return node.currentURI.spec;
} else if (node instanceof Ci.nsIDOMHTMLMediaElement) {
return (node.currentSrc || node.src);
}
return "";
},
_buildMenu: function(x, y) {
// now walk up the tree and for each node look for any context menu items that apply
let element = this._target;
this.menuitems = [];
while (element) {
// First check for any html5 context menus that might exist...
this._addHTMLContextMenuItemsForElement(element);
// then check for any context menu items registered in the ui.
this._addNativeContextMenuItems(element, x, y);
// walk up the tree and find more items to show
element = element.parentNode;
}
},
// Actually shows the native context menu by passing a list of context menu items to
// show to the Java.
_show: function(aEvent) {
@ -2249,69 +2246,83 @@ var NativeWindow = {
this._innerShow(popupNode, aEvent.clientX, aEvent.clientY);
},
_innerShow: function(aTarget, aX, aY) {
_findTitle: function(node) {
let title = "";
while(node && !title) {
title = this._getTitle(node);
node = node.parentNode;
}
return title;
},
_getItems: function(target) {
return this._getItemsInList(target, this.menuitems);
},
_getItemsInList: function(target, list) {
let itemArray = [];
for (let i = 0; i < list.length; i++) {
let t = target;
while(t) {
if (list[i].matches(t)) {
let val = list[i].getValue(t);
// hidden menu items will return null from getValue
if (val) {
itemArray.push(val);
break;
}
}
t = t.parentNode;
}
}
return itemArray;
},
_innerShow: function(target, x, y) {
Haptic.performSimpleAction(Haptic.LongPress);
// spin through the tree looking for a title for this context menu
let node = aTarget;
let title ="";
while(node && !title) {
if (node.hasAttribute && node.hasAttribute("title")) {
title = node.getAttribute("title");
} else if ((node instanceof Ci.nsIDOMHTMLAnchorElement && node.href) ||
(node instanceof Ci.nsIDOMHTMLAreaElement && node.href)) {
title = this._getLinkURL(node);
} else if (node instanceof Ci.nsIImageLoadingContent && node.currentURI) {
title = node.currentURI.spec;
} else if (node instanceof Ci.nsIDOMHTMLMediaElement) {
title = (node.currentSrc || node.src);
}
node = node.parentNode;
}
let title = this._findTitle(target);
// convert this.menuitems object to an array for sending to native code
let itemArray = [];
for (let i = 0; i < this.menuitems.length; i++) {
let val = this.menuitems[i].getValue(aTarget);
// hidden menu items will return null from getValue
if (val)
itemArray.push(val);
}
if (itemArray.length == 0)
return;
this.menuitems.sort((a,b) => {
if (a.order == b.order) return 0;
return (a.order > b.order) ? 1 : -1;
});
let prompt = new Prompt({
window: aTarget.ownerDocument.defaultView,
window: target.ownerDocument.defaultView,
title: title
}).setSingleChoiceItems(itemArray)
.show((function(data) {
});
let items = this._getItems(target);
prompt.setSingleChoiceItems(items);
prompt.show(this._promptDone.bind(this, target, x, y, items));
},
_promptDone: function(target, x, y, items, data) {
if (data.button == -1) {
// prompt was cancelled
return;
}
let selectedId = itemArray[data.button].id;
let selectedItem = this._getMenuItemForId(selectedId);
let selectedItemId = items[data.list[0]].id;
let selectedItem = this._containsItem(selectedItemId);
this.menuitems = null;
if (selectedItem && selectedItem.callback) {
if (selectedItem.matches) {
if (!selectedItem || !selectedItem.matches || !selectedItem.callback) {
return;
}
// for menuitems added using the native UI, pass the dom element that matched that item to the callback
while (aTarget) {
if (selectedItem.matches(aTarget, aX, aY)) {
selectedItem.callback.call(selectedItem, aTarget, aX, aY);
while (target) {
if (selectedItem.matches(target, x, y)) {
selectedItem.callback(target, x, y);
break;
}
aTarget = aTarget.parentNode;
target = target.parentNode;
}
} else {
// if this was added using the html5 context menu api, just click on the context menu item
selectedItem.callback.call(selectedItem, aTarget, aX, aY);
}
}
}).bind(this));
},
handleEvent: function(aEvent) {
@ -2343,7 +2354,7 @@ var NativeWindow = {
aElement instanceof Ci.nsIDOMHTMLLinkElement ||
aElement.getAttributeNS(kXLinkNamespace, "type") == "simple")) {
try {
let url = NativeWindow.contextmenus._getLinkURL(aElement);
let url = this._getLinkURL(aElement);
return Services.io.newURI(url, null, null);
} catch (e) {}
}
@ -8264,3 +8275,108 @@ var Tabs = {
}
},
};
function ContextMenuItem(args) {
this.id = uuidgen.generateUUID().toString();
this.args = args;
}
ContextMenuItem.prototype = {
get order() {
return this.args.order || 0;
},
matches: function(elt, x, y) {
return this.args.selector.matches(elt, x, y);
},
callback: function(elt) {
this.args.callback(elt);
},
addVal: function(name, elt, defaultValue) {
if (!(name in this.args))
return defaultValue;
if (typeof this.args[name] == "function")
return this.args[name](elt);
return this.args[name];
},
getValue: function(elt) {
return {
id: this.id,
label: this.addVal("label", elt),
shareData: this.addVal("shareData", elt),
icon: this.addVal("icon", elt),
isGroup: this.addVal("isGroup", elt, false),
inGroup: this.addVal("inGroup", elt, false),
disabled: this.addVal("disabled", elt, false),
selected: this.addVal("selected", elt, false),
isParent: this.addVal("isParent", elt, false),
};
}
}
function HTMLContextMenuItem(elt, target) {
ContextMenuItem.call(this, { });
this.menuElementRef = Cu.getWeakReference(elt);
this.targetElementRef = Cu.getWeakReference(target);
}
HTMLContextMenuItem.prototype = Object.create(ContextMenuItem.prototype, {
order: {
value: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER
},
matches: {
value: function(target) {
let t = this.targetElementRef.get();
return t === target;
},
},
callback: {
value: function(target) {
let elt = this.menuElementRef.get();
if (!elt) {
return;
}
// If this is a menu item, show a new context menu with the submenu in it
if (elt instanceof Ci.nsIDOMHTMLMenuElement) {
try {
NativeWindow.contextmenus.menuitems = [];
NativeWindow.contextmenus._addHTMLContextMenuItemsForMenu(elt, target);
NativeWindow.contextmenus._innerShow(target);
} catch(ex) {
Cu.reportError(ex);
}
} else {
// otherwise just click the menu item
elt.click();
}
},
},
getValue: {
value: function(target) {
let elt = this.menuElementRef.get();
if (!elt)
return null;
if (elt.hasAttribute("hidden"))
return null;
return {
id: this.id,
icon: elt.icon,
label: elt.label,
disabled: elt.disabled,
menu: elt instanceof Ci.nsIDOMHTMLMenuElement
};
}
},
});

View File

@ -1362,7 +1362,13 @@ ThreadActor.prototype = {
if (line == null ||
line < 0 ||
this.dbg.findScripts({ url: url }).length == 0) {
return { error: "noScript" };
return {
error: "noScript",
message: "Requested setting a breakpoint on "
+ url + ":" + line
+ (column != null ? ":" + column : "")
+ " but there is no Debugger.Script at that location"
};
}
let response = this._createAndStoreBreakpoint({
@ -1450,6 +1456,10 @@ ThreadActor.prototype = {
if (scripts.length == 0) {
return {
error: "noScript",
message: "Requested setting a breakpoint on "
+ aLocation.url + ":" + aLocation.line
+ (aLocation.column != null ? ":" + aLocation.column : "")
+ " but there is no Debugger.Script at that location",
actor: actor.actorID
};
}