Merge f-t to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 16 Mar 2014 08:36:21 -0700
changeset 191023 907cacf958de4127d7ff8c510819e15e26f4b9e9
parent 191019 4c35e029aa48bbeb762218d73145b9b23f545ff6 (current diff)
parent 191022 f785999a6144a0886c7f39d8598f68e7e6d82163 (diff)
child 191029 d14ffbbfac7b1fc6860e16ed87f224cefd915c53
child 191037 f4ce22b60a1856771ce5878b7a9564ec2f93cafd
child 191062 2098a82908fab199691088dea8a5e5f19223fc0a
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge f-t to m-c
--- 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
@@ -18,49 +18,62 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gThreadClient = gDebugger.gThreadClient;
     gEvents = gDebugger.EVENTS;
 
     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
           }),
           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.
         yield promise.all([
           rdpInvoke(bp1, bp1.remove),
           rdpInvoke(bp2, bp1.remove),
           rdpInvoke(bp3, bp1.remove),
         ]);
--- 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,5 +1,6 @@
+debugger;
 var a = (function(){
   var b = 9;
   console.log("x", b);
   return b;
 })();
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -806,19 +806,19 @@ this.UITour = {
       highlighter.parentElement.hidden = false;
 
       let targetRect = aTargetEl.getBoundingClientRect();
       let highlightHeight = targetRect.height;
       let highlightWidth = targetRect.width;
       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 {
         highlighter.style.borderRadius = "";
       }
 
       highlighter.style.height = highlightHeight + "px";
       highlighter.style.width = highlightWidth + "px";
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1913,48 +1913,46 @@ var NativeWindow = {
             delete this.doorhanger._callbacks[id];
           }
         }
       }
     }
   },
   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);
     },
 
     uninit: function() {
       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) {
       delete this.items[aId];
     },
 
     SelectorContext: function(aSelector) {
       return {
@@ -2109,214 +2107,227 @@ var NativeWindow = {
     },
   
     set _target(aTarget) {
       if (aTarget)
         this._targetRef = Cu.getWeakReference(aTarget);
       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) {
       let popupNode = this._target;
       this._target = null;
       if (aEvent.defaultPrevented || !popupNode) {
         return;
       }
       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;
-      }
-
-      // 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;
+      let title = this._findTitle(target);
+
+      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 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 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;
         }
-
-        let selectedId = itemArray[data.button].id;
-        let selectedItem = this._getMenuItemForId(selectedId);
-
-        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);
-          }
-        }
-      }).bind(this));
+        target = target.parentNode;
+      }
     },
 
     handleEvent: function(aEvent) {
       BrowserEventHandler._cancelTapHighlight();
       aEvent.target.ownerDocument.defaultView.removeEventListener("contextmenu", this, false);
       this._show(aEvent);
     },
 
@@ -2338,17 +2349,17 @@ var NativeWindow = {
 
     _getLink: function(aElement) {
       if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
           ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
           (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) ||
           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) {}
       }
       return null;
     },
 
     _disableInGuest: function _disableInGuest(selector) {
       return {
@@ -8259,8 +8270,113 @@ var Tabs = {
   // For debugging
   dump: function(aPrefix) {
     let tabs = BrowserApp.tabs;
     for (let i = 0; i < tabs.length; i++) {
       dump(aPrefix + " | " + "Tab [" + tabs[i].browser.contentWindow.location.href + "]: lastTouchedAt:" + tabs[i].lastTouchedAt + ", zombie:" + tabs[i].browser.__SS_restore);
     }
   },
 };
+
+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
+      };
+    }
+  },
+});
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -1357,17 +1357,23 @@ ThreadActor.prototype = {
           line: originalLine,
           column: originalColumn } = aRequest.location;
 
     let locationPromise = this.sources.getGeneratedLocation(aRequest.location);
     return locationPromise.then(({url, line, column}) => {
       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({
         url: url,
         line: line,
         column: column
       });
       // If the original location of our generated location is different from
@@ -1445,16 +1451,20 @@ ThreadActor.prototype = {
       this.threadLifetimePool.addActor(actor);
     }
 
     // Find all scripts matching the given location
     let scripts = this.dbg.findScripts(aLocation);
     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
       };
     }
 
    /**
     * For each script, if the given line has at least one entry point, set a
     * breakpoint on the bytecode offets for each of them.
     */