Bug 611555 (1/2) - Use code from Easy Reading to reflow text on zoom [r=mfinkle]
authorMatt Brubeck <mbrubeck@mozilla.com>
Mon, 27 Dec 2010 21:23:14 -0800
changeset 67166 15c875e62aaf3a95464a24011073087638a19f3b
parent 67165 2aa9aa129d62a757168dde4bc454e52347d891bf
child 67167 90d212d91d473dabf6274132708819b1805663ef
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)
reviewersmfinkle
bugs611555
Bug 611555 (1/2) - Use code from Easy Reading to reflow text on zoom [r=mfinkle]
mobile/chrome/content/browser.js
mobile/chrome/content/content.js
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -61,17 +61,17 @@ const kDefaultBrowserWidth = 980;
 const kBrowserFormZoomLevelMin = 0.8;
 const kBrowserFormZoomLevelMax = 2.0;
 const kBrowserViewZoomLevelPrecision = 10000;
 
 const kDefaultMetadata = { autoSize: false, allowZoom: true, autoScale: true };
 
 // Override sizeToContent in the main window. It breaks things (bug 565887)
 window.sizeToContent = function() {
-  Components.utils.reportError("window.sizeToContent is not allowed in this window");
+  Cu.reportError("window.sizeToContent is not allowed in this window");
 }
 
 #ifdef MOZ_CRASH_REPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
 function onDebugKeyPress(ev) {
@@ -487,17 +487,17 @@ var Browser = {
     os.removeObserver(ContentCrashObserver, "ipc:content-shutdown");
     os.removeObserver(MemoryObserver, "memory-pressure");
     os.removeObserver(BrowserSearch, "browser-search-engine-modified");
 
     window.controllers.removeController(this);
     window.controllers.removeController(BrowserUI);
   },
 
-  getHomePage: function (aOptions) {
+  getHomePage: function getHomePage(aOptions) {
     aOptions = aOptions || { useDefault: false };
 
     let url = "about:home";
     try {
       let prefs = aOptions.useDefault ? Services.prefs.getDefaultBranch(null) : Services.prefs;
       url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data;
     }
     catch(e) { }
@@ -988,27 +988,25 @@ var Browser = {
   /** Rect should be in browser coordinates. */
   _getZoomLevelForRect: function _getZoomLevelForRect(rect) {
     const margin = 15;
     return this.selectedTab.clampZoomLevel(window.innerWidth / (rect.width + margin * 2));
   },
 
   /**
    * Find an appropriate zoom rect for an element bounding rect, if it exists.
-   * @return Rect in viewport coordinates
-   * */
+   * @return Rect in viewport coordinates, or null
+   */
   _getZoomRectForRect: function _getZoomRectForRect(rect, y) {
     let oldZoomLevel = getBrowser().scale;
     let zoomLevel = this._getZoomLevelForRect(rect);
     let zoomRatio = oldZoomLevel / zoomLevel;
 
-    // Don't zoom in a marginal amount, but be more lenient for the first zoom.
-    // > 2/3 means operation increases the zoom level by less than 1.5
-    // > 9/10 means operation increases the zoom level by less than 1.1
-    let zoomTolerance = (this.selectedTab.isDefaultZoomLevel()) ? .9 : .6666;
+    // Don't zoom in a marginal amount.
+    let zoomTolerance = .95;
     if (zoomRatio >= zoomTolerance)
       return null;
     else
       return this._getZoomRectForPoint(rect.center().x, y, zoomLevel);
   },
 
   /**
    * Find a good zoom rectangle for point that is specified in browser coordinates.
@@ -1073,16 +1071,33 @@ var Browser = {
     let tab = this.selectedTab;
     if (tab.allowZoom && !tab.isDefaultZoomLevel()) {
       let zoomLevel = tab.getDefaultZoomLevel();
       let zoomRect = this._getZoomRectForPoint(cX, cY, zoomLevel);
       this.animatedZoomTo(zoomRect);
     }
   },
 
+  // The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
+  // This is higher on higher-dpi displays, so pages stay about the same physical size.
+  getScaleRatio: function getScaleRatio() {
+    let prefValue = Services.prefs.getIntPref("browser.viewport.scaleRatio");
+    if (prefValue > 0)
+      return prefValue / 100;
+
+    let dpi = this.windowUtils.displayDPI;
+    if (dpi < 200) // Includes desktop displays, and LDPI and MDPI Android devices
+      return 1;
+    else if (dpi < 300) // Includes Nokia N900, and HDPI Android devices
+      return 1.5;
+
+    // For very high-density displays like the iPhone 4, calculate an integer ratio.
+    return Math.floor(dpi / 150);
+  },
+
   /**
    * Convenience function for getting the scrollbox position off of a
    * scrollBoxObject interface.  Returns the actual values instead of the
    * wrapping objects.
    *
    * @param scroller a scrollBoxObject on which to call scroller.getPosition()
    */
   getScrollboxPosition: function getScrollboxPosition(scroller) {
@@ -1119,18 +1134,20 @@ var Browser = {
                         json.ctrlKey, json.altKey, json.shiftKey, json.metaKey,
                         json.keyCode, json.charCode)
         document.getElementById("mainKeyset").dispatchEvent(event);
         break;
 
       case "Browser:ZoomToPoint:Return":
         // JSON-ified rect needs to be recreated so the methods exist
         let rect = Rect.fromRect(json.rect);
-        if (!this.zoomToPoint(json.x, json.y, rect))
+        if (!this.zoomToPoint(json.x, json.y, rect)) {
+          browser.messageManager.sendAsyncMessage("Browser:ResetZoom", {});
           this.zoomFromPoint(json.x, json.y);
+        }
         break;
 
       case "scroll":
         if (browser == this.selectedBrowser) {
           this.hideTitlebar();
           this.hideSidebars();
         }
         break;
@@ -1290,17 +1307,17 @@ Browser.MainDragger.prototype = {
 function nsBrowserAccess()
 {
 }
 
 nsBrowserAccess.prototype = {
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports))
       return this;
-    throw Components.results.NS_NOINTERFACE;
+    throw Cr.NS_NOINTERFACE;
   },
 
   _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
     let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
     if (isExternal && aURI && aURI.schemeIs("chrome"))
       return null;
 
     let loadflags = isExternal ?
@@ -1603,27 +1620,25 @@ const ContentTouchHandler = {
     // TapUp event with XULDocument as a target occurs on desktop when the
     // mouse is released outside of the Fennec window, and because XULDocument
     // does not have a classList properties, just check it exists first to
     // prevent a warning
     let target = aEvent.target;
     return target && ("classList" in target && target.classList.contains("inputHandler"));
   },
 
-  _dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY, aModifiers) {
-    let aX = aX || 0;
-    let aY = aY || 0;
+  _dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY, aOptions) {
     let browser = getBrowser();
-    let pos = browser.transformClientToBrowser(aX, aY);
-    browser.messageManager.sendAsyncMessage(aName, {
-      x: pos.x,
-      y: pos.y,
-      modifiers: aModifiers || null,
-      messageId: this._messageId
-    });
+    let pos = browser.transformClientToBrowser(aX || 0, aY || 0);
+
+    let json = aOptions || {};
+    json.x = pos.x;
+    json.y = pos.y;
+    json.messageId = this._messageId;
+    browser.messageManager.sendAsyncMessage(aName, json);
   },
 
   tapDown: function tapDown(aX, aY) {
     // Ensure that the content process has gets an activate event
     let browser = getBrowser();
     let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
     browser.focus();
     try {
@@ -1643,22 +1658,28 @@ const ContentTouchHandler = {
   },
 
   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, aModifiers);
+      this._dispatchMouseEvent("Browser:MouseUp", aX, aY, { modifiers: aModifiers });
   },
 
   tapDouble: function tapDouble(aX, aY, aModifiers) {
     this._clearPendingMessages();
-    this._dispatchMouseEvent("Browser:ZoomToPoint", aX, aY);
+
+    let tab = Browser.selectedTab;
+    if (!tab.allowZoom)
+      return;
+
+    let width = window.innerWidth / Browser.getScaleRatio();
+    this._dispatchMouseEvent("Browser:ZoomToPoint", aX, aY, { width: width });
   },
 
   tapLong: function tapLong() {
     if (this._contextMenu) {
       if (ContextHelper.showPopup(this._contextMenu)) {
         // Stop all input sequences
         let event = document.createEvent("Events");
         event.initEvent("CancelTouchSequence", true, false);
@@ -1693,17 +1714,17 @@ ContentCustomKeySender.prototype = {
         keyCode: aEvent.keyCode,
         charCode: (aEvent.type != "keydown") ? aEvent.charCode : null,
         modifiers: this._parseModifiers(aEvent)
       });
     }
   },
 
   _parseModifiers: function _parseModifiers(aEvent) {
-    const masks = Components.interfaces.nsIDOMNSEvent;
+    const masks = Ci.nsIDOMNSEvent;
     var mval = 0;
     if (aEvent.shiftKey)
       mval |= masks.SHIFT_MASK;
     if (aEvent.ctrlKey)
       mval |= masks.CONTROL_MASK;
     if (aEvent.altKey)
       mval |= masks.ALT_MASK;
     if (aEvent.metaKey)
@@ -2270,17 +2291,17 @@ var ContentCrashObserver = {
     }, 0, this);
   }
 };
 
 var MemoryObserver = {
   observe: function mo_observe() {
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIDOMWindowUtils).garbageCollect();
-    Components.utils.forceGC();
+    Cu.forceGC();
   }
 };
 
 function getNotificationBox(aBrowser) {
   return Browser.getNotificationBox(aBrowser);
 }
 
 function importDialog(aParent, aSrc, aArguments) {
@@ -2507,17 +2528,17 @@ ProgressController.prototype = {
   },
 
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.nsIWebProgressListener) ||
         aIID.equals(Ci.nsISupportsWeakReference) ||
         aIID.equals(Ci.nsISupports))
       return this;
 
-    throw Components.results.NS_ERROR_NO_INTERFACE;
+    throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
   _networkStart: function _networkStart() {
     this._tab.startLoading();
 
     if (this._tab == Browser.selectedTab) {
       BrowserUI.update(TOOLBARSTATE_LOADING);
 
@@ -2671,17 +2692,17 @@ Tab.prototype = {
     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 = this.getScaleRatio();
+      let scaleRatio = aMetadata.scaleRatio = Browser.getScaleRatio();
 
       if ("defaultZoom" in aMetadata && aMetadata.defaultZoom > 0)
         aMetadata.defaultZoom *= scaleRatio;
       if ("minZoom" in aMetadata && aMetadata.minZoom > 0)
         aMetadata.minZoom *= scaleRatio;
       if ("maxZoom" in aMetadata && aMetadata.maxZoom > 0)
         aMetadata.maxZoom *= scaleRatio;
     }
@@ -2730,33 +2751,16 @@ Tab.prototype = {
         viewportW = kDefaultBrowserWidth;
         viewportH = kDefaultBrowserWidth * (screenH / screenW);
       }
     }
 
     browser.setWindowSize(viewportW, viewportH);
   },
 
-  // The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
-  // This is higher on higher-dpi displays, so pages stay about the same physical size.
-  getScaleRatio: function getScaleRatio() {
-    let prefValue = Services.prefs.getIntPref("browser.viewport.scaleRatio");
-    if (prefValue > 0)
-      return prefValue / 100;
-
-    let dpi = Browser.windowUtils.displayDPI;
-    if (dpi < 200) // Includes desktop displays, and LDPI and MDPI Android devices
-      return 1;
-    else if (dpi < 300) // Includes Nokia N900, and HDPI Android devices
-      return 1.5;
-
-    // For very high-density displays like the iPhone 4, calculate an integer ratio.
-    return Math.floor(dpi / 150);
-  },
-
   restoreViewportPosition: function restoreViewportPosition(aOldWidth, aNewWidth) {
     let browser = this._browser;
     let pos = browser.getPosition();
 
     // zoom to keep the same portion of the document visible
     let oldScale = browser.scale;
     let newScale = this.clampZoomLevel(oldScale * aNewWidth / aOldWidth);
     browser.scale = newScale;
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -1,14 +1,15 @@
 // This stays here because otherwise it's hard to tell if there's a parsing error
 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");
 
 let gFocusManager = Cc["@mozilla.org/focus-manager;1"]
   .getService(Ci.nsIFocusManager);
 
 let XULDocument = Ci.nsIDOMXULDocument;
 let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
@@ -265,17 +266,17 @@ ProgressController.prototype = {
 
   QueryInterface: function QueryInterface(aIID) {
     if (aIID.equals(Ci.nsIWebProgressListener) ||
         aIID.equals(Ci.nsISupportsWeakReference) ||
         aIID.equals(Ci.nsISupports)) {
         return this;
     }
 
-    throw Components.results.NS_ERROR_NO_INTERFACE;
+    throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
   start: function start() {
     let flags = Ci.nsIWebProgress.NOTIFY_STATE_NETWORK;
     let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIWebProgress);
     webProgress.addProgressListener(this, flags);
   },
@@ -292,23 +293,25 @@ ProgressController.prototype = {
 function Content() {
   addMessageListener("Browser:Blur", this);
   addMessageListener("Browser:KeyEvent", this);
   addMessageListener("Browser:MouseDown", this);
   addMessageListener("Browser:MouseOver", this);
   addMessageListener("Browser:MouseUp", this);
   addMessageListener("Browser:SaveAs", this);
   addMessageListener("Browser:ZoomToPoint", this);
+  addMessageListener("Browser:ResetZoom", this);
   addMessageListener("Browser:MozApplicationCache:Fetch", this);
 
   if (Util.isParentProcess())
     addEventListener("DOMActivate", this, true);
 
   addEventListener("MozApplicationManifest", this, false);
   addEventListener("command", this, false);
+  addEventListener("pagehide", this, false);
 
   this._progressController = new ProgressController(this);
   this._progressController.start();
 
   this._formAssistant = new FormAssistant();
 }
 
 Content.prototype = {
@@ -331,24 +334,25 @@ Content.prototype = {
         let doc = aEvent.originalTarget;
         sendAsyncMessage("Browser:MozApplicationManifest", {
           location: doc.documentURIObject.spec,
           manifest: doc.documentElement.getAttribute("manifest"),
           charset: doc.characterSet
         });
         break;
       }
+
       case "command": {
         // Don't trust synthetic events
         if (!aEvent.isTrusted)
           return;
-    
+
         let ot = aEvent.originalTarget;
         let errorDoc = ot.ownerDocument;
-    
+
         // If the event came from an ssl error page, it is probably either the "Add
         // Exception…" or "Get me out of here!" button
         if (/^about:certerror\?e=nssBadCert/.test(errorDoc.documentURI)) {
           let perm = errorDoc.getElementById("permanentExceptionButton");
           let temp = errorDoc.getElementById("temporaryExceptionButton"); 
           if (ot == temp || ot == perm) {
             let action = (ot == perm ? "permanent" : "temporary");
             sendAsyncMessage("Browser:CertException", { url: errorDoc.location.href, action: action });
@@ -360,16 +364,21 @@ Content.prototype = {
         else if (/^about:neterror\?e=netOffline/.test(errorDoc.documentURI)) {
           if (ot == errorDoc.getElementById("errorTryAgain")) {
             // Make sure we're online before attempting to load
             Util.forceOnline();
           }
         }
         break;
       }
+
+      case "pagehide":
+        if (aEvent.target == content.document)
+          this._setTextZoom(1);
+        break;
     }
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     let json = aMessage.json;
     let x = json.x;
     let y = json.y;
     let modifiers = json.modifiers;
@@ -492,22 +501,28 @@ Content.prototype = {
         break;
 
       case "Browser:ZoomToPoint": {
         let rect = null;
         let element = elementFromPoint(x, y);
         let win = element.ownerDocument.defaultView;
         while (element && win.getComputedStyle(element,null).display == "inline")
           element = element.parentNode;
-        if (element)
+        if (element) {
           rect = getBoundingContentRect(element);
+          this._setTextZoom(Math.max(1, rect.width / json.width));
+        }
         sendAsyncMessage("Browser:ZoomToPoint:Return", { x: x, y: y, rect: rect });
         break;
       }
 
+      case "Browser:ResetZoom":
+        this._setTextZoom(1);
+        break;
+
       case "Browser:MozApplicationCache:Fetch": {
         let currentURI = Services.io.newURI(json.location, json.charset, null);
         let manifestURI = Services.io.newURI(json.manifest, json.charset, currentURI);
         let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
                             .getService(Ci.nsIOfflineCacheUpdateService);
         updateService.scheduleUpdate(manifestURI, currentURI, content);
         break;
       }
@@ -536,16 +551,21 @@ Content.prototype = {
       }
     }
 
     let scrollOffset = Util.getScrollOffset(content);
     let windowUtils = Util.getWindowUtils(content);
     windowUtils.sendMouseEventToWindow(aName, aX - scrollOffset.x, aY - scrollOffset.y, 0, 1, 0, true);
   },
 
+  _setTextZoom: function _setTextZoom(aZoom) {
+    let viewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
+    viewer.textZoom = aZoom;
+  },
+
   startLoading: function startLoading() {
     this._loading = true;
   },
 
   stopLoading: function stopLoading() {
     this._loading = false;
   },
 };
@@ -880,17 +900,17 @@ var FormSubmitObserver = {
       // We don't need to send any data along
       sendAsyncMessage("Browser:FormSubmit", {});
   },
 
   QueryInterface : function(aIID) {
     if (!aIID.equals(Ci.nsIFormSubmitObserver) &&
         !aIID.equals(Ci.nsISupportsWeakReference) &&
         !aIID.equals(Ci.nsISupports))
-      throw Components.results.NS_ERROR_NO_INTERFACE;
+      throw Cr.NS_ERROR_NO_INTERFACE;
     return this;
   }
 };
 
 FormSubmitObserver.init();
 
 var FindHandler = {
   get _fastFind() {