Bug 623765 - Use active pseudoclass for tap highlighting [r=vingtetun]
authorWes Johnston <wjohnston@mozilla.com>
Tue, 11 Jan 2011 16:21:06 -0800
changeset 67245 4b30267ee228a4eb0f54c0de13c057c543a45974
parent 67244 567bfbcded0dad9fb8e5d22539ec9ca18ee74b28
child 67246 3db9a94867ddafa936437d07d7e7ea02a0359b06
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvingtetun
bugs623765
Bug 623765 - Use active pseudoclass for tap highlighting [r=vingtetun]
mobile/chrome/content/browser.js
mobile/chrome/content/content.css
mobile/chrome/content/content.js
mobile/chrome/content/notification.xml
mobile/themes/core/firstRun.css
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -766,18 +766,16 @@ var Browser = {
 
     if (this._selectedTab == tab) {
       // Deck does not update its selectedIndex when children
       // are removed. See bug 602708
       Elements.browsers.selectedPanel = tab.notification;
       return;
     }
 
-    TapHighlightHelper.hide();
-
     if (this._selectedTab) {
       this._selectedTab.pageScrollOffset = this.getScrollboxPosition(this.pageScrollboxScroller);
 
       // Make sure we leave the toolbar in an unlocked state
       if (this._selectedTab.isLoading())
         BrowserUI.unlockToolbar();
     }
 
@@ -1341,18 +1339,16 @@ Browser.WebProgress.prototype = {
         let spec = json.location;
         let location = spec.split("#")[0]; // Ignore fragment identifier changes.
 
         if (tab == Browser.selectedTab)
           BrowserUI.updateURI();
 
         let locationHasChanged = (location != tab.browser.lastLocation);
         if (locationHasChanged) {
-          TapHighlightHelper.hide();
-
           Browser.getNotificationBox(tab.browser).removeTransientNotifications();
           tab.resetZoomLevel();
           tab.hostChanged = true;
           tab.browser.lastLocation = location;
           tab.browser.userTypedValue = "";
 
 #ifdef MOZ_CRASH_REPORTER
           if (CrashReporter.enabled)
@@ -1590,30 +1586,25 @@ const ContentTouchHandler = {
         let contextMenu = { name: aMessage.name, json: aMessage.json, target: aMessage.target };
         if (ContextHelper.showPopup(contextMenu)) {
           // Stop all input sequences
           let event = document.createEvent("Events");
           event.initEvent("CancelTouchSequence", true, false);
           document.dispatchEvent(event);
         }
         break;
-
-      case "Browser:Highlight": {
-        let rects = aMessage.json.rects.map(function(r) new Rect(r.left, r.top, r.width, r.height));
-        TapHighlightHelper.show(rects);
-        break;
-      }
     }
   },
 
   /** Invalidates any messages received from content that are sensitive to time. */
   _clearPendingMessages: function _clearPendingMessages() {
     this._messageId++;
-    TapHighlightHelper.hide(0);
     this._contextMenu = null;
+    let browser = getBrowser();
+    browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
   },
 
   /**
    * Check if the event concern the browser content
    */
   _targetIsContent: function _targetIsContent(aEvent) {
     // TapUp event with XULDocument as a target occurs on desktop when the
     // mouse is released outside of the Fennec window, and because XULDocument
@@ -1645,22 +1636,22 @@ const ContentTouchHandler = {
     this._dispatchMouseEvent("Browser:MouseDown", aX, aY);
   },
 
   tapOver: function tapOver(aX, aY) {
     this._dispatchMouseEvent("Browser:MouseOver", aX, aY);
   },
 
   tapUp: function tapUp(aX, aY) {
-    TapHighlightHelper.hide(200);
     this._contextMenu = null;
+    let browser = getBrowser();
+    browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
   },
 
   tapSingle: function tapSingle(aX, aY, aModifiers) {
-    TapHighlightHelper.hide(200);
     this._contextMenu = null;
 
     // Cancel the mouse click if we are showing a context menu
     if (!ContextHelper.popupState)
       this._dispatchMouseEvent("Browser:MouseUp", aX, aY, { modifiers: aModifiers });
   },
 
   tapDouble: function tapDouble(aX, aY, aModifiers) {
@@ -2451,22 +2442,16 @@ Tab.prototype = {
   },
 
   get inputHandler() {
     if (!this._notification)
       return null;
     return this._notification.inputHandler;
   },
 
-  get overlay() {
-    if (!this._notification)
-      return null;
-    return this._notification.overlay;
-  },
-
   /** Update browser styles when the viewport metadata changes. */
   updateViewportMetadata: function updateViewportMetadata(aMetadata) {
     if (aMetadata && aMetadata.autoScale) {
       let scaleRatio = aMetadata.scaleRatio = Browser.getScaleRatio();
 
       if ("defaultZoom" in aMetadata && aMetadata.defaultZoom > 0)
         aMetadata.defaultZoom *= scaleRatio;
       if ("minZoom" in aMetadata && aMetadata.minZoom > 0)
@@ -2830,85 +2815,8 @@ var ViewableAreaObserver = {
       setTimeout(function() {
         let event = document.createEvent("Events");
         event.initEvent("SizeChanged", true, false);
         Elements.browsers.dispatchEvent(event);
       }, 0);
     }
   }
 };
-
-var TapHighlightHelper = {
-  get _overlay() {
-    if (Browser.selectedTab)
-      return Browser.selectedTab.overlay;
-    return null;
-  },
-
-  show: function show(aRects) {
-    let overlay = this._overlay;
-    if (!overlay)
-      return;
-
-    let browser = getBrowser();
-    let scroll = browser.getPosition();
-
-    let canvasArea = aRects.reduce(function(a, b) {
-      return a.expandToContain(b);
-    }, new Rect(0, 0, 0, 0)).map(function(val) val * browser.scale)
-                            .translate(-scroll.x, -scroll.y);
-
-    overlay.setAttribute("width", canvasArea.width);
-    overlay.setAttribute("height", canvasArea.height);
-
-    let ctx = overlay.getContext("2d");
-    ctx.save();
-    ctx.translate(-canvasArea.left, -canvasArea.top);
-    ctx.scale(browser.scale, browser.scale);
-
-    overlay.setAttribute("left", canvasArea.left);
-    overlay.setAttribute("top", canvasArea.top);
-    ctx.clearRect(0, 0, canvasArea.width, canvasArea.height);
-    ctx.fillStyle = "rgba(0, 145, 255, .5)";
-    for (let i = aRects.length - 1; i >= 0; i--) {
-      let rect = aRects[i];
-      ctx.fillRect(rect.left - scroll.x / browser.scale, rect.top - scroll.y / browser.scale, rect.width, rect.height);
-    }
-    ctx.restore();
-    overlay.style.display = "block";
-
-    mozRequestAnimationFrame(this);
-  },
-
-  /**
-   * Hide the highlight. aGuaranteeShowMsecs specifies how many milliseconds the
-   * highlight should be shown before it disappears.
-   */
-  hide: function hide(aGuaranteeShowMsecs) {
-    if (!this._overlay || this._overlay.style.display == "none")
-      return;
-
-    this._guaranteeShow = Math.max(0, aGuaranteeShowMsecs);
-    if (this._guaranteeShow) {
-      // _shownAt is set once highlight has been painted
-      if (this._shownAt)
-        setTimeout(this._hide.bind(this), Math.max(0, this._guaranteeShow - (mozAnimationStartTime - this._shownAt)));
-    } else {
-      this._hide();
-    }
-  },
-
-  /** Helper function that hides popup immediately. */
-  _hide: function _hide() {
-    this._shownAt = 0;
-    this._guaranteeShow = 0;
-    this._overlay.style.display = "none";
-  },
-
-  onBeforePaint: function handleEvent(aTimeStamp) {
-    this._shownAt = aTimeStamp;
-
-    // hide has been called, so hide the tap highlight after it has
-    // been shown for a moment.
-    if (this._guaranteeShow)
-      this.hide(this._guaranteeShow);
-  }
-};
--- a/mobile/chrome/content/content.css
+++ b/mobile/chrome/content/content.css
@@ -317,8 +317,20 @@ select[disabled] > input[type="button"] 
   padding: 1px 7px 1px 7px;
 }
 
 /* -moz-touch-enabled? media elements */
 video > xul|videocontrols,
 audio > xul|videocontrols {
   -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#touchControls");
 }
+
+*:-moz-any-link:active,
+*[role=button]:active,
+button:active,
+input:active,
+option:active,
+select:active,
+label:active,
+textarea:active {
+  background-color: #8db8d8;
+  outline: 2px solid #8db8d8;
+}
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -2,19 +2,22 @@
 dump("###################################### content loaded\n");
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 let Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-let gFocusManager = Cc["@mozilla.org/focus-manager;1"]
-  .getService(Ci.nsIFocusManager);
+XPCOMUtils.defineLazyServiceGetter(this, "gFocusManager",
+  "@mozilla.org/focus-manager;1", "nsIFocusManager");
+XPCOMUtils.defineLazyServiceGetter(this, "gDOMUtils",
+  "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
 
 let XULDocument = Ci.nsIDOMXULDocument;
 let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
 let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
 let HTMLFrameElement = Ci.nsIDOMHTMLFrameElement;
 let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
 let HTMLOptionElement = Ci.nsIDOMHTMLOptionElement;
 
@@ -23,16 +26,18 @@ const kViewportMinScale  = 0;
 const kViewportMaxScale  = 10;
 const kViewportMinWidth  = 200;
 const kViewportMaxWidth  = 10000;
 const kViewportMinHeight = 223;
 const kViewportMaxHeight = 10000;
 
 const kReferenceDpi = 240; // standard "pixel" size used in some preferences
 
+const kStateActive = 0x00000001; // :active pseudoclass for elements
+
 /** Watches for mouse click in content and redirect them to the best found target **/
 const ElementTouchHelper = {
   get radius() {
     let prefs = Services.prefs;
     delete this.radius;
     return this.radius = { "top": prefs.getIntPref("browser.ui.touch.top"),
                            "right": prefs.getIntPref("browser.ui.touch.right"),
                            "bottom": prefs.getIntPref("browser.ui.touch.bottom"),
@@ -230,16 +235,17 @@ let Content = {
     this._isZoomedToElement = false;
 
     addMessageListener("Browser:Blur", this);
     addMessageListener("Browser:KeyEvent", this);
     addMessageListener("Browser:MouseOver", this);
     addMessageListener("Browser:MouseLong", this);
     addMessageListener("Browser:MouseDown", this);
     addMessageListener("Browser:MouseUp", this);
+    addMessageListener("Browser:MouseCancel", this);
     addMessageListener("Browser:SaveAs", this);
     addMessageListener("Browser:ZoomToPoint", this);
     addMessageListener("Browser:MozApplicationCache:Fetch", this);
 
     if (Util.isParentProcess())
       addEventListener("DOMActivate", this, true);
 
     addEventListener("MozApplicationManifest", this, false);
@@ -357,27 +363,22 @@ let Content = {
           return;
 
         // There is no need to have a feedback for disabled element
         let isDisabled = element instanceof HTMLOptionElement ? (element.disabled || element.parentNode.disabled) : element.disabled;
         if (isDisabled)
           return;
 
         // Calculate the rect of the active area
-        let targetElement = null;
-        if (element.mozMatchesSelector("*:-moz-any-link, *[role=button],button,input,option,select,textarea,label"))
-          targetElement = element;
-        else if (element.mozMatchesSelector("*:-moz-any-link *"))
-          targetElement = element.parentNode;
+        this._doTapHighlight(element);
+        break;
+      }
 
-        if (targetElement) {
-          let rect = getOverflowContentBoundingRect(targetElement);
-          let highlightRects = [{ left: rect.x, top: rect.y, width: rect.width, height: rect.height }];
-          sendAsyncMessage("Browser:Highlight", { rects: highlightRects, messageId: json.messageId });
-        }
+      case "Browser:MouseCancel": {
+        this._cancelTapHighlight();
         break;
       }
 
       case "Browser:MouseLong": {
         let element = elementFromPoint(x, y);
         if (!element)
           return;
 
@@ -385,16 +386,17 @@ let Content = {
 
         let event = content.document.createEvent("PopupEvents");
         event.initEvent("contextmenu", true, true);
         element.dispatchEvent(event);
         break;
       }
 
       case "Browser:MouseUp": {
+        this._cancelTapHighlight();
         this._formAssistant.focusSync = true;
         let element = elementFromPoint(x, y);
         if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
           let uri = Util.getHrefForElement(element);
           if (uri)
             sendAsyncMessage("Browser:OpenURI", { uri: uri,
                                                   referrer: element.ownerDocument.documentURIObject.spec });
         } else if (!this._formAssistant.open(element)) {
@@ -482,16 +484,24 @@ let Content = {
         let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
                             .getService(Ci.nsIOfflineCacheUpdateService);
         updateService.scheduleUpdate(manifestURI, currentURI, content);
         break;
       }
     }
   },
 
+  _doTapHighlight: function _doTapHighlight(aElt) {
+    gDOMUtils.setContentState(aElt, kStateActive);
+  },
+
+  _cancelTapHighlight: function _cancelTapHighlight() {
+    gDOMUtils.setContentState(content.document.documentElement, kStateActive);
+  },
+
   _sendMouseEvent: function _sendMouseEvent(aName, aElement, aX, aY) {
     // the element can be out of the aX/aY point because of the touch radius
     if (!(aElement instanceof HTMLHtmlElement)) {
       let isTouchClick = true;
       let rects = getContentClientRects(aElement);
       for (let i = 0; i < rects.length; i++) {
         let rect = rects[i];
         if ((aX > rect.left && aX < (rect.left + rect.width)) &&
--- a/mobile/chrome/content/notification.xml
+++ b/mobile/chrome/content/notification.xml
@@ -14,33 +14,26 @@
   <binding id="stacked-notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
     <content>
       <xul:stack xbl:inherits="hidden=notificationshidden">
         <xul:spacer/>
         <children includes="notification"/>
       </xul:stack>
       <xul:stack flex="1">
         <children/>
-        <html:canvas anonid="content-overlay"/>
         <html:div flex="1" class="input-overlay" anonid="input-overlay"/>
       </xul:stack>
     </content>
     <implementation>
       <property name="inputHandler">
         <getter>
           return document.getAnonymousElementByAttribute(this, "anonid", "input-overlay");
         </getter>
       </property>
 
-      <property name="overlay">
-        <getter>
-          return document.getAnonymousElementByAttribute(this, "anonid", "content-overlay");
-        </getter>
-      </property>
-
       <property name="customDragger">
         <getter>
           return this.parentNode.customDragger;
         </getter>
       </property>
     </implementation>
   </binding>
 
--- a/mobile/themes/core/firstRun.css
+++ b/mobile/themes/core/firstRun.css
@@ -173,16 +173,17 @@ img {
   padding: 20px 0;
   -moz-border-radius: 10px;
   background: #d8ecf1;
 }
 
 #firstrun ul#recommended li a:active {
   text-decoration: none;
   background: #b5d7e0;
+  outline: none;
 }
 
 #firstrun ul#recommended li b {
   display: block;
   padding: 90px 10px 0 10px;
   background-repeat: no-repeat;
   background-image: url("chrome://browser/content/firstrun/features.png");
 }