From e8d5a62a481aafd4531fa2d8c0ffaa99cff727bc Mon Sep 17 00:00:00 2001 From: Blake Winton Date: Sat, 15 Mar 2014 18:29:38 -0700 Subject: [PATCH 1/3] Bug 983653 - UITour: Make the highlight effect a circle on the bookmarks combo button. ui-r=shorlander, r=MattN --- browser/modules/UITour.jsm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/modules/UITour.jsm b/browser/modules/UITour.jsm index e17fd83c3fc..8ae7182bbcf 100644 --- a/browser/modules/UITour.jsm +++ b/browser/modules/UITour.jsm @@ -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 { From 9cdf2a2c0fbd90d8edba908ce543f7362c0c606a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Sat, 15 Mar 2014 18:48:55 -0700 Subject: [PATCH 2/3] Bug 981258 - Set the breakpoints in browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js before executng the IIFE so that the IIFE's Debugger.Script can't be GC'd before the breakpoints are set; r=past --- ...-break-on-last-line-of-script-on-reload.js | 37 +++++++++++++------ ...-break-on-last-line-of-script-on-reload.js | 1 + toolkit/devtools/server/actors/script.js | 12 +++++- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js index d33d00cfc87..57881460dd0 100644 --- a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js +++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js @@ -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. diff --git a/browser/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js b/browser/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js index f2078e9c40b..a8e8a79731c 100644 --- a/browser/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js +++ b/browser/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js @@ -1,3 +1,4 @@ +debugger; var a = (function(){ var b = 9; console.log("x", b); diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 3eb542a62a2..841d3d47eca 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -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 }; } From 00a63a34fb2c23d25f933b0ed25722510bdb704e Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Fri, 21 Feb 2014 14:05:00 -0800 Subject: [PATCH 3/3] Bug 942270 - Refactor context menu code. r=bnicholson --- mobile/android/chrome/content/browser.js | 436 ++++++++++++++--------- 1 file changed, 276 insertions(+), 160 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 646fb0fabad..338adcb4589 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -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,130 +2112,129 @@ 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 - })) { + mode: SelectionHandler.SELECT_AT_POINT, + x: x, + y: y + })) { SelectionHandler.attachCaret(target); } } } }, + _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) { - if (data.button == -1) { - // prompt was cancelled - return; - } + }); - let selectedId = itemArray[data.button].id; - let selectedItem = this._getMenuItemForId(selectedId); + let items = this._getItems(target); + prompt.setSingleChoiceItems(items); + prompt.show(this._promptDone.bind(this, target, x, y, items)); + }, - this.menuitems = null; - if (selectedItem && selectedItem.callback) { - if (selectedItem.matches) { - // 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); - break; - } - aTarget = aTarget.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); - } + _promptDone: function(target, x, y, items, data) { + if (data.button == -1) { + // prompt was cancelled + return; + } + + let selectedItemId = items[data.list[0]].id; + let selectedItem = this._containsItem(selectedItemId); + this.menuitems = null; + + 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 (target) { + if (selectedItem.matches(target, x, y)) { + selectedItem.callback(target, x, y); + break; } - }).bind(this)); + target = target.parentNode; + } }, 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 + }; + } + }, +});