Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Fri, 06 Jan 2012 17:03:12 -0800
changeset 109119 266b7ffc925d7b04bd7ecca549dde55047758676
parent 109117 2f73f3274b267adb5fd6447ddb640b31fc930189 (current diff)
parent 85173 5a446202be5fa2633e4cc1c6113a6ebded512882 (diff)
child 109120 df3fab333dbc7011bc12816f1cf3d521107465bf
push idunknown
push userunknown
push dateunknown
milestone12.0a1
Merge from mozilla-central.
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/tabbrowser.xml
browser/components/nsBrowserGlue.js
build/mobile/devicemanagerADB.py
content/base/src/nsDocument.cpp
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsStubImageDecoderObserver.cpp
content/base/src/nsWebSocket.cpp
content/canvas/src/nsCanvasRenderingContext2D.cpp
content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/html/content/src/nsTextEditorState.cpp
content/media/nsBuiltinDecoder.cpp
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/svg/content/src/nsSVGFilters.cpp
content/svg/content/src/nsSVGFilters.h
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindowCommands.cpp
dom/indexedDB/IndexedDatabaseManager.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/src/storage/nsDOMStoragePersistentDB.cpp
dom/workers/RuntimeService.cpp
editor/libeditor/text/nsTextEditRules.cpp
editor/libeditor/text/tests/Makefile.in
gfx/layers/Layers.cpp
gfx/layers/Layers.h
gfx/layers/d3d10/LayerManagerD3D10.cpp
gfx/layers/d3d9/LayerManagerD3D9.cpp
gfx/layers/opengl/LayerManagerOGL.cpp
gfx/src/nsFontMetrics.cpp
gfx/tests/gfxFontSelectionTest.cpp
gfx/thebes/gfxASurface.h
gfx/thebes/gfxCoreTextShaper.cpp
gfx/thebes/gfxCoreTextShaper.h
gfx/thebes/gfxDWriteShaper.cpp
gfx/thebes/gfxDWriteShaper.h
gfx/thebes/gfxFT2Fonts.cpp
gfx/thebes/gfxFT2Fonts.h
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxGDIFont.cpp
gfx/thebes/gfxGDIFont.h
gfx/thebes/gfxGDIShaper.cpp
gfx/thebes/gfxGDIShaper.h
gfx/thebes/gfxHarfBuzzShaper.cpp
gfx/thebes/gfxHarfBuzzShaper.h
gfx/thebes/gfxMacFont.cpp
gfx/thebes/gfxMacFont.h
gfx/thebes/gfxPangoFonts.cpp
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPlatformFontList.cpp
gfx/thebes/gfxTextRunCache.cpp
gfx/thebes/gfxTextRunCache.h
gfx/thebes/gfxTextRunWordCache.cpp
gfx/thebes/gfxTextRunWordCache.h
gfx/thebes/gfxUniscribeShaper.cpp
gfx/thebes/gfxUniscribeShaper.h
gfx/thebes/gfxWindowsPlatform.cpp
gfx/thebes/gfxWindowsPlatform.h
image/src/RasterImage.cpp
image/src/VectorImage.cpp
image/src/imgRequest.cpp
image/src/imgRequestProxy.cpp
ipc/chromium/src/chrome/common/ipc_message_utils.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/Parser.cpp
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsinterpinlines.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsopcode.tbl
js/src/jsprvtd.h
js/src/jsscope.cpp
js/src/jsstr.cpp
js/src/jsxdrapi.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastOps.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/MonoIC.cpp
js/src/methodjit/PolyIC.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
js/src/shell/js.cpp
layout/base/nsImageLoader.cpp
layout/base/nsImageLoader.h
layout/base/nsPresShell.cpp
layout/base/nsPresShell.h
layout/generic/nsBulletFrame.cpp
layout/generic/nsBulletFrame.h
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/generic/nsSelection.cpp
layout/generic/nsTextFrameThebes.cpp
layout/generic/nsTextFrameUtils.h
layout/generic/nsTextRunTransformations.cpp
layout/generic/nsTextRunTransformations.h
layout/svg/base/src/nsSVGGlyphFrame.cpp
layout/svg/base/src/nsSVGImageFrame.cpp
layout/xul/base/src/nsImageBoxFrame.cpp
layout/xul/base/src/nsImageBoxFrame.h
layout/xul/base/src/tree/src/nsTreeImageListener.cpp
layout/xul/base/src/tree/src/nsTreeImageListener.h
mobile/android/base/GeckoApp.java
mobile/android/base/Makefile.in
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsDiskCacheDevice.cpp
netwerk/cache/nsDiskCacheDevice.h
netwerk/cookie/nsCookieService.cpp
netwerk/protocol/http/nsHttpPipeline.cpp
storage/public/mozStorageHelper.h
storage/src/VacuumManager.cpp
storage/src/mozStorageConnection.cpp
testing/mochitest/runtests.py
testing/mochitest/runtestsremote.py
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/telemetry/TelemetryHistograms.h
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/content/widgets/videocontrols.xml
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
toolkit/system/gnome/nsAlertsIconListener.cpp
toolkit/themes/winstripe/mozapps/extensions/extensions.css
toolkit/themes/winstripe/mozapps/extensions/selectAddons.css
widget/android/nsWindow.cpp
widget/cocoa/nsMenuItemIconX.mm
xpcom/base/nsMemoryReporterManager.cpp
--- a/.gitignore
+++ b/.gitignore
@@ -18,17 +18,17 @@ ID
 /configure
 /config.cache
 /config.log
 
 # Empty marker file that's generated when we check out NSS
 security/manager/.nss.checkout
 
 # Build directories
-obj/*
+obj*/
 
 # Build directories for js shell
 */_DBG.OBJ/
 */_OPT.OBJ/
 
 # SpiderMonkey configury
 js/src/configure
 js/src/autom4te.cache
--- a/accessible/tests/mochitest/events/test_scroll.xul
+++ b/accessible/tests/mochitest/events/test_scroll.xul
@@ -27,17 +27,17 @@
 
   <script type="application/javascript"
           src="chrome://mochikit/content/chrome-harness.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
     ////////////////////////////////////////////////////////////////////////////
-    // Hack to make xul:tabbrowser work
+    // Hacks to make xul:tabbrowser work
 
     const Ci = Components.interfaces;
     const CC = Components.classes;
 
     Components.utils.import("resource://gre/modules/Services.jsm");
 
     var handleDroppedLink = null;
 
@@ -48,16 +48,18 @@
     };
 
     var gURLBar = {
       focused: false
     };
 
     var gFindBarInitialized = false;
 
+    function goSetCommandEnabled() {}
+
     ////////////////////////////////////////////////////////////////////////////
     // Tests
 
     function getTabDocument()
     {
       return getNode("tabBrowser").selectedBrowser.contentDocument;
     }
 
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1,8 +1,10 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -55,18 +57,18 @@ XPCOMUtils.defineLazyGetter(Services, 's
 });
 XPCOMUtils.defineLazyGetter(Services, 'fm', function() {
   return Cc['@mozilla.org/focus-manager;1']
            .getService(Ci.nsIFocusManager);
 });
 
 // In order to use http:// scheme instead of file:// scheme
 // (that is much more restricted) the following code kick-off
-// a local http server listening on http://127.0.0.1:8888 and
-// http://localhost:8888.
+// a local http server listening on http://127.0.0.1:7777 and
+// http://localhost:7777.
 function startupHttpd(baseDir, port) {
   const httpdURL = 'chrome://browser/content/httpd.js';
   let httpd = {};
   Services.scriptloader.loadSubScript(httpdURL, httpd);
   let server = new httpd.nsHttpServer();
   server.registerDirectory('/', new LocalFile(baseDir));
   server.registerContentType('appcache', 'text/cache-manifest');
   server.start(port);
@@ -86,16 +88,19 @@ function addPermissions(urls) {
     permissions.forEach(function(permission) {
       Services.perms.add(uri, permission, allow);
     });
   });
 }
 
 
 var shell = {
+  // FIXME/bug 678695: this should be a system setting
+  preferredScreenBrightness: 1.0,
+
   get home() {
     delete this.home;
     return this.home = document.getElementById('homescreen');
   },
 
   get homeURL() {
     try {
       let homeSrc = Services.env.get('B2G_HOMESCREEN');
@@ -134,17 +139,17 @@ var shell = {
       let fileScheme = 'file://';
       if (homeURL.substring(0, fileScheme.length) == fileScheme) {
         homeURL = homeURL.replace(fileScheme, '');
 
         let baseDir = homeURL.split('/');
         baseDir.pop();
         baseDir = baseDir.join('/');
 
-        const SERVER_PORT = 8888;
+        const SERVER_PORT = 6666;
         startupHttpd(baseDir, SERVER_PORT);
 
         let baseHost = 'http://localhost';
         homeURL = homeURL.replace(baseDir, baseHost + ':' + SERVER_PORT);
       }
       addPermissions([homeURL]);
     } catch (e) {
       let msg = 'Fatal error during startup: [' + e + '[' + homeURL + ']';
@@ -190,27 +195,28 @@ var shell = {
   handleEvent: function shell_handleEvent(evt) {
     switch (evt.type) {
       case 'keypress':
         switch (evt.keyCode) {
           case evt.DOM_VK_HOME:
             this.sendEvent(this.home.contentWindow, 'home');
             break;
           case evt.DOM_VK_SLEEP:
-            screen.mozEnabled = !screen.mozEnabled;
+            this.toggleScreen();
             break;
           case evt.DOM_VK_ESCAPE:
             if (evt.defaultPrevented)
               return;
             this.doCommand('cmd_close');
             break;
         }
         break;
       case 'load':
         this.home.removeEventListener('load', this, true);
+        this.turnScreenOn();
         this.sendEvent(window, 'ContentStart');
         break;
       case 'MozApplicationManifest':
         try {
           if (!Services.prefs.getBoolPref('browser.cache.offline.enable'))
             return;
 
           let contentWindow = evt.originalTarget.defaultView;
@@ -243,17 +249,31 @@ var shell = {
         }
         break;
     }
   },
   sendEvent: function shell_sendEvent(content, type, details) {
     let event = content.document.createEvent('CustomEvent');
     event.initCustomEvent(type, true, true, details ? details : {});
     content.dispatchEvent(event);
-  }
+  },
+  toggleScreen: function shell_toggleScreen() {
+    if (screen.mozEnabled)
+      this.turnScreenOff();
+    else
+      this.turnScreenOn();
+  },
+  turnScreenOff: function shell_turnScreenOff() {
+    screen.mozEnabled = false;
+    screen.mozBrightness = 0.0;
+  },
+  turnScreenOn: function shell_turnScreenOn() {
+    screen.mozEnabled = true;
+    screen.mozBrightness = this.preferredScreenBrightness;
+  },
 };
 
 (function VirtualKeyboardManager() {
   let activeElement = null;
   let isKeyboardOpened = false;
 
   let constructor = {
     handleEvent: function vkm_handleEvent(evt) {
--- a/b2g/chrome/content/shell.xul
+++ b/b2g/chrome/content/shell.xul
@@ -35,16 +35,17 @@
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         id="shell"
         width="480" height="800"
+        windowtype="navigator:browser"
 #ifdef ANDROID
         sizemode="fullscreen"
 #endif
         style="background: black; overflow: hidden;"
         onload="shell.start();"
         onunload="shell.stop();">
 
   <script type="application/javascript" src="chrome://browser/content/commandUtil.js"/>
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -1,17 +1,17 @@
 pref("startup.homepage_override_url","http://www.mozilla.org/projects/%APP%/%VERSION%/whatsnew/");
 pref("startup.homepage_welcome_url","http://www.mozilla.org/projects/%APP%/%VERSION%/firstrun/");
 // The time interval between checks for a new version (in seconds)
-pref("app.update.interval", 7200); // 2 hours
+pref("app.update.interval", 3600); // 1 hour
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
 pref("app.update.download.backgroundInterval", 60);
-// Give the user x seconds to react before showing the big UI. default=12 hours
-pref("app.update.promptWaitTime", 43200);
+// Give the user x seconds to react before showing the big UI. default=1 hour
+pref("app.update.promptWaitTime", 3600);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "http://nightly.mozilla.org/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "http://www.mozilla.org/projects/%APP%/");
 
 // Release notes and vendor URLs
--- a/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain.js
+++ b/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain.js
@@ -37,13 +37,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Test added with bug 460086 to test the behavior of the new API that was added
  * to remove all traces of visiting a site.
  */
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 function run_test() {
   PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing-wrapper;1";
   load("do_test_removeDataFromDomain.js");
   do_test();
+
+  // Shutdown the download manager.
+  Services.obs.notifyObservers(null, "quit-application", null);
 }
--- a/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain_activeDownloads.js
+++ b/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain_activeDownloads.js
@@ -37,13 +37,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Test added with bug 460086 to test the behavior of the new API that was added
  * to remove all traces of visiting a site.
  */
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 function run_test() {
   PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing;1";
   load("do_test_removeDataFromDomain_activeDownloads.js");
   do_test();
+
+  // Shutdown the download manager.
+  Services.obs.notifyObservers(null, "quit-application", null);
 }
--- a/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain.js
+++ b/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain.js
@@ -37,13 +37,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Test added with bug 460086 to test the behavior of the new API that was added
  * to remove all traces of visiting a site.
  */
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 function run_test() {
   PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing;1";
   load("do_test_removeDataFromDomain.js");
   do_test();
+
+  // Shutdown the download manager.
+  Services.obs.notifyObservers(null, "quit-application", null);
 }
--- a/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain_activeDownloads.js
+++ b/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain_activeDownloads.js
@@ -37,13 +37,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Test added with bug 460086 to test the behavior of the new API that was added
  * to remove all traces of visiting a site.
  */
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 function run_test() {
   PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing;1";
   load("do_test_removeDataFromDomain_activeDownloads.js");
   do_test();
+
+  // Shutdown the download manager.
+  Services.obs.notifyObservers(null, "quit-application", null);
 }
--- a/browser/themes/winstripe/browser-aero.css
+++ b/browser/themes/winstripe/browser-aero.css
@@ -32,17 +32,17 @@
     -moz-margin-start: 1px;
   }
 
   .panel-promo-message {
     font-style: italic;
   }
 }
 
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   #navigator-toolbox > toolbar:not(:-moz-lwtheme),
   #browser-bottombox:not(:-moz-lwtheme) {
     background-color: @customToolbarColor@;
   }
 
   .tabbrowser-tab:not(:-moz-lwtheme),
   .tabs-newtab-button:not(:-moz-lwtheme) {
     background-image: @toolbarShadowOnTab@, @bgTabTexture@,
@@ -77,17 +77,17 @@
   }
 
   .menu-accel,
   .menu-iconic-accel {
     color: graytext;
   }
 }
 
-@media all and (-moz-windows-compositor) {
+@media (-moz-windows-compositor) {
   /* These should be hidden w/ glass enabled. Windows draws its own buttons. */
   .titlebar-button {
     display: none;
   }
 
   #main-window[sizemode="maximized"] #titlebar-buttonbox {
     -moz-margin-end: 3px;
   }
@@ -343,17 +343,17 @@
 
 #minimize-button:-moz-locale-dir(rtl),
 #restore-button:-moz-locale-dir(rtl),
 #close-button:-moz-locale-dir(rtl) {
   -moz-transform: scaleX(-1);
 }
 
 /* ::::: splitmenu highlight style that imitates Windows 7 start menu ::::: */
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   .splitmenu-menuitem,
   .splitmenu-menu {
     -moz-appearance: none;
     padding-top: 2px;
     padding-bottom: 2px;
     border: 1px solid transparent;
   }
   .splitmenu-menuitem {
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -160,24 +160,24 @@
   color: white;
   text-shadow: 0 0 1px rgba(0,0,0,.7),
                0 1px 1.5px rgba(0,0,0,.5);
   font-weight: bold;
   padding: 0 1.5em .05em;
   margin: 0 0 2px;
 }
 
-@media all and (-moz-windows-classic) {
+@media (-moz-windows-classic) {
   #appmenu-button {
     margin-bottom: 1px;
   }
 }
 
 %ifndef WINSTRIPE_AERO
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   #main-window[sizemode="normal"] #appmenu-button {
     margin-bottom: 5px;
   }
 }
 %endif
 
 #appmenu-button:hover:active,
 #appmenu-button[open] {
@@ -310,17 +310,17 @@
 .appmenu-edit-button[disabled="true"] {
   opacity: .3;
 }
 
 #appmenuPrimaryPane {
   -moz-border-end: 1px solid ThreeDShadow;
 }
 
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   #appmenu-popup {
     -moz-appearance: none;
     background: white;
     border: 1px solid ThreeDShadow;
   }
   #appmenuPrimaryPane {
     background-color: rgba(255,255,255,0.5);
     padding: 2px;
@@ -478,17 +478,17 @@
 #main-window[sizemode="normal"] > #titlebar {
   -moz-appearance: -moz-window-titlebar;
 }
 
 #main-window[sizemode="maximized"] > #titlebar {
   -moz-appearance: -moz-window-titlebar-maximized;
 }
 
-@media all and (-moz-windows-classic) {
+@media (-moz-windows-classic) {
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container {
     margin-top: 4px;
   }
 }
 
 #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box;
 }
@@ -1209,17 +1209,17 @@ toolbar[mode="full"] .toolbarbutton-1 > 
 }
 
 #urlbar {
   width: 7em;
   min-width: 7em;
   -moz-padding-end: 2px;
 }
 
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   #urlbar,
   .searchbar-textbox {
     @navbarTextboxCustomBorder@
   }
 }
 
 #urlbar:-moz-lwtheme,
 .searchbar-textbox:-moz-lwtheme {
@@ -1707,17 +1707,17 @@ richlistitem[type~="action"][actiontype=
 #editBMPanel_folderTree {
   min-width: 27em;
 }
 
 .panel-promo-box {
   margin: 16px 0 -2px;
 }
 
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   .panel-promo-box {
     margin: 8px -16px -16px;
     padding: 8px 16px;
 %ifndef WINSTRIPE_AERO
     border-bottom-left-radius: 6px;
     border-bottom-right-radius: 6px;
 %endif
     background-color: #f1f5fb;
@@ -1790,17 +1790,17 @@ richlistitem[type~="action"][actiontype=
 
 #TabsToolbar:not(:-moz-lwtheme),
 #TabsToolbar[tabsontop=false] {
   background-image:
     -moz-linear-gradient(bottom, @toolbarShadowColor@ 1px, rgba(0,0,0,.05) 1px, transparent 50%);
 }
 
 %ifndef WINSTRIPE_AERO
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   #main-window[sizemode=normal] #TabsToolbar {
     padding-left: 2px;
     padding-right: 2px;
   }
 }
 %endif
 
 .tabbrowser-tab,
@@ -1822,17 +1822,17 @@ richlistitem[type~="action"][actiontype=
 
 .tabbrowser-tab:hover,
 .tabs-newtab-button:hover {
   background-image: @toolbarShadowOnTab@, @bgTabTextureHover@,
                     -moz-linear-gradient(-moz-dialog, -moz-dialog);
 }
 
 %ifndef WINSTRIPE_AERO
-@media all and (-moz-windows-theme: luna-blue) {
+@media (-moz-windows-theme: luna-blue) {
   .tabbrowser-tab,
   .tabs-newtab-button {
     background-image: @toolbarShadowOnTab@,
                       -moz-linear-gradient(hsla(51,34%,89%,.9), hsla(51,15%,79%,.9) 1px, hsla(51,9%,68%,.9));
   }
 
   .tabbrowser-tab:hover,
   .tabs-newtab-button:hover {
@@ -1951,17 +1951,17 @@ richlistitem[type~="action"][actiontype=
 }
 
 .tab-close-button[selected="true"] {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 /* Tab scrollbox arrow, tabstrip new tab and all-tabs buttons */
 
-@media all and (-moz-touch-enabled) {
+@media (-moz-touch-enabled) {
   .tabbrowser-arrowscrollbox > .scrollbutton-up,
   .tabbrowser-arrowscrollbox > .scrollbutton-down,
   #TabsToolbar .toolbarbutton-1 {
     min-width: 8.1mozmm;
   }
 
   .tabs-newtab-button {
     min-width: 10mozmm;
--- a/browser/themes/winstripe/places/organizer-aero.css
+++ b/browser/themes/winstripe/places/organizer-aero.css
@@ -16,27 +16,27 @@
 
   #placesToolbar {
     -moz-appearance: none;
     background-color: -moz-Dialog;
     color: -moz-dialogText;
   }
 }
 
-@media all and (-moz-windows-compositor) {
+@media (-moz-windows-compositor) {
   #placesToolbox {
     border-top: none;
   }
 
   #placesToolbar {
     background-image: -moz-linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
   }
 }
 
-@media all and (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) {
   #placesView,
   #searchModifiers,
   #infoPane,
   #placesList,
   #placeContent {
     background-color: #EEF3FA;
   }
 
--- a/build/mobile/devicemanagerADB.py
+++ b/build/mobile/devicemanagerADB.py
@@ -107,27 +107,31 @@ class DeviceManagerADB(DeviceManager):
   #  success: remoteDir
   #  failure: None
   def pushDir(self, localDir, remoteDir):
     # adb "push" accepts a directory as an argument, but if the directory
     # contains symbolic links, the links are pushed, rather than the linked
     # files; we either zip/unzip or push file-by-file to get around this 
     # limitation
     try:
+      if (not self.dirExists(remoteDir)):
+        self.mkDirs(remoteDir+"/x")
       if (self.useZip):
         localZip = tempfile.mktemp()+".zip"
         remoteZip = remoteDir + "/adbdmtmp.zip"
         subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
         self.pushFile(localZip, remoteZip)
         os.remove(localZip)
-        self.checkCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir])
+        data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read()
         self.checkCmdAs(["shell", "rm", remoteZip])
+        if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)):
+          print "zip/unzip failure: falling back to normal push"
+          self.useZip = False
+          self.pushDir(localDir, remoteDir)
       else:
-        if (not self.dirExists(remoteDir)):
-          self.mkDirs(remoteDir+"/x")
         for root, dirs, files in os.walk(localDir, followlinks='true'):
           relRoot = os.path.relpath(root, localDir)
           for file in files:
             localFile = os.path.join(root, file)
             remoteFile = remoteDir + "/"
             if (relRoot!="."):
               remoteFile = remoteFile + relRoot + "/"
             remoteFile = remoteFile + file
@@ -604,17 +608,17 @@ class DeviceManagerADB(DeviceManager):
       if (self.fileExists(devroot + "/sanity/tmpfile")):
         print "will execute commands via run-as " + packageName
         self.packageName = packageName
         self.useRunAs = True
       self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
       self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"])
       
   def isUnzipAvailable(self):
-    data = self.runCmd(["shell", "unzip"]).stdout.read()
+    data = self.runCmdAs(["shell", "unzip"]).stdout.read()
     if (re.search('Usage', data)):
       return True
     else:
       return False
 
   def isLocalZipAvailable(self):
     try:
       subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
--- a/build/mobile/robocop/Actions.java.in
+++ b/build/mobile/robocop/Actions.java.in
@@ -39,22 +39,31 @@
 
 package @ANDROID_PACKAGE_NAME@;
 import java.util.List;
 
 public interface Actions {
   public enum SpecialKey {
     DOWN, UP, LEFT, RIGHT, ENTER
   }
+
+  public interface EventExpecter {
+    /** Blocks until the event has been received. Subsequent calls will return immediately. */
+    public void blockForEvent();
+    /** Polls to see if the event has been received. Once this returns true, subsequent calls will also return true. */
+    public boolean eventReceived();
+  }
+
   /**
-   * Waits for a gecko event to be sent from the Gecko instance.
+   * Listens for a gecko event to be sent from the Gecko instance.
+   * The returned object can be used to test if the event has been
+   * received. Note that only one event is listened for.
    * 
    * @param geckoEvent The geckoEvent JSONObject's type
    */
-
-  void waitForGeckoEvent(String geckoEvent);
+  EventExpecter expectGeckoEvent(String geckoEvent);
   // Send the string kewsToSend to the application 
   void sendKeys(String keysToSend);
   //Send any of the above keys to the element
   void sendSpecialKey(SpecialKey button);
 
   void drag(int startingX, int endingX, int startingY, int endingY);
 }
--- a/build/mobile/robocop/Driver.java.in
+++ b/build/mobile/robocop/Driver.java.in
@@ -33,27 +33,30 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
+
 import java.util.List;
+import android.app.Activity;
 
 public interface Driver {
   /**
    * Find the first Element using the given method.
    * 
+   * @param activity The activity the element belongs to
    * @param name The name of the element
    * @return The first matching element on the current context
    * @throws RoboCopException If no matching elements are found
    */
-  Element findElement(String name);
+  Element findElement(Activity activity, String name);
 
   /**
    * Sets up scroll handling so that data is received from the extension.
    */
   void setupScrollHandling();
 
   int getPageHeight();
   int getScrollHeight();
--- a/build/mobile/robocop/FennecNativeActions.java.in
+++ b/build/mobile/robocop/FennecNativeActions.java.in
@@ -79,19 +79,16 @@ public class FennecNativeActions impleme
   private Class gel;
   private Class ge;
   private Class gas;
   private Method registerGEL;
   private Method unregisterGEL;
   private Method sendGE;
 
 
-  // If waiting for an event.
-  private SynchronousQueue waitqueue = new SynchronousQueue<Boolean>();
-
   public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation){
     this.solo = robocop;
     this.instr = instrumentation;
     // Set up reflexive access of java classes and methods.
     try {
       classLoader = activity.getClassLoader();
       gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
       ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent");
@@ -111,61 +108,106 @@ public class FennecNativeActions impleme
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      } catch (IllegalArgumentException e) {
        e.printStackTrace();
      }
   }
 
   class wakeInvocationHandler implements InvocationHandler {
-    public wakeInvocationHandler(){};
+    private final GeckoEventExpecter mEventExpecter;
+
+    public wakeInvocationHandler(GeckoEventExpecter expecter) {
+      mEventExpecter = expecter;
+    }
+
     public Object invoke(Object proxy, Method method, Object[] args) {
       String methodName = method.getName();
       //Depending on the method, return a completely different type.
       if(methodName.equals("toString")) {
         return "wakeInvocationHandler";
       }
       if(methodName.equals("equals")) {
         return this == args[0];
       }
       if(methodName.equals("clone")) {
         return this;
       }
       if(methodName.equals("hashCode")) {
         return 314;
       }
       Log.i("Robocop", "Waking up on "+methodName);
-      waitqueue.offer(new Boolean(true));
+      mEventExpecter.notifyOfEvent();
       return null;
     }
   }
+
+  class GeckoEventExpecter implements EventExpecter {
+    private final String mGeckoEvent;
+    private final Object[] mRegistrationParams;
+    private boolean mEventReceived;
+
+    GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
+      mGeckoEvent = geckoEvent;
+      mRegistrationParams = registrationParams;
+    }
+
+    public synchronized void blockForEvent() {
+      while (! mEventReceived) {
+        try {
+          this.wait();
+        } catch (InterruptedException ie) {
+          ie.printStackTrace();
+          break;
+        }
+      }
+      Log.i("Robocop", "unblocked on expecter for " + mGeckoEvent);
+    }
+
+    public synchronized boolean eventReceived() {
+      return mEventReceived;
+    }
+
+    void notifyOfEvent() {
+      try {
+        unregisterGEL.invoke(null, mRegistrationParams);
+      } catch (IllegalAccessException e) {
+        e.printStackTrace();
+      } catch (InvocationTargetException e) {
+        e.printStackTrace();
+      }
+      Log.i("Robocop", "received event " + mGeckoEvent);
+      synchronized (this) {
+        mEventReceived = true;
+        this.notifyAll();
+      }
+    }
+  }
   
-  public void waitForGeckoEvent(String geckoEvent) {
+  public EventExpecter expectGeckoEvent(String geckoEvent) {
     Log.i("Robocop", "waiting for "+geckoEvent);
     try {
       Class [] interfaces = new Class[1];
       interfaces[0] = gel;
       Object[] finalParams = new Object[2];
       finalParams[0] = geckoEvent;
      
-      wakeInvocationHandler wIH = new wakeInvocationHandler();
+      GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
+      wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
       Object proxy = Proxy.newProxyInstance(classLoader, interfaces, wIH);
       finalParams[1] = proxy;
       registerGEL.invoke(null, finalParams);
       
-      waitqueue.take();
-      unregisterGEL.invoke(null, finalParams);
+      return expecter;
     } catch (IllegalAccessException e) {
       e.printStackTrace();
     } catch (InvocationTargetException e) {
       e.printStackTrace();
-    } catch (InterruptedException e) {
-      e.printStackTrace();
     }
-    Log.i("Robocop", "wait ends for: "+geckoEvent);
+    return null;
   }
 
   public void sendSpecialKey(SpecialKey button) {
     switch( button) {
       case DOWN:
         instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
         break;
       case UP:
--- a/build/mobile/robocop/FennecNativeDriver.java.in
+++ b/build/mobile/robocop/FennecNativeDriver.java.in
@@ -162,18 +162,17 @@ public class FennecNativeDriver implemen
   }
   public int getGeckoWidth() {
     if(!geckoInfo) {
       getGeckoInfo();
     }
     return geckoWidth;
   }
 
-  @Override
-  public Element findElement(String name) {
+  public Element findElement(Activity activity, String name) {
     if (name == null)
       throw new IllegalArgumentException("Can not findElements when passed a null");
     if (locators.containsKey(name)){
       return new FennecNativeElement(Integer.decode((String)locators.get(name)), activity, solo);
     }
     throw new RoboCopException("Element does not exist in the list");
   }
 
--- a/build/mobile/robocop/FennecNativeElement.java.in
+++ b/build/mobile/robocop/FennecNativeElement.java.in
@@ -51,64 +51,59 @@ import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.TextSwitcher;
 import android.app.Instrumentation;
 import android.util.Log;
 import com.jayway.android.robotium.solo.Solo;
 import java.util.concurrent.SynchronousQueue;
 
 public class FennecNativeElement implements Element {
+  private final Activity mActivity;
   private Integer id;
-  private Activity currentActivity;
   private Solo robocop;
 
   public FennecNativeElement(Integer id, Activity activity, Solo solo){
     this.id = id;
+    mActivity = activity;
     robocop = solo;
-    currentActivity = activity;
   }
 
   public Integer getId() {
     return id;
   }
 
-  @Override
   public void click() {
     final SynchronousQueue syncQueue = new SynchronousQueue();
-    currentActivity = robocop.getCurrentActivity();
-    currentActivity.runOnUiThread(
+    mActivity.runOnUiThread(
         new Runnable() {
           public void run() {
-            View view = (View)currentActivity.findViewById(id);
+            View view = (View)mActivity.findViewById(id);
             if(view != null) {
               view.performClick();
             } else {
               throw new RoboCopException("click: unable to find view "+id); 
             }
             syncQueue.offer(new Object());
           }
         });
     try {
       syncQueue.take();
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
   }
 
   private Object text;
-  private Activity elementActivity;
 
-  @Override
   public String getText() {
-    elementActivity = robocop.getCurrentActivity();
     final SynchronousQueue syncQueue = new SynchronousQueue();
-    elementActivity.runOnUiThread(
+    mActivity.runOnUiThread(
         new Runnable() {
           public void run() {
-            View v = elementActivity.findViewById(id);
+            View v = mActivity.findViewById(id);
             if(v instanceof EditText) {
               EditText et = (EditText)v;
               text = et.getEditableText();
             }else if(v instanceof TextSwitcher) {
               TextSwitcher ts = (TextSwitcher)v;
               ts.getNextView();
               text = ((TextView)ts.getCurrentView()).getText();
             }else if(v instanceof ViewGroup) {
@@ -136,14 +131,32 @@ public class FennecNativeElement impleme
       e.printStackTrace();
     }
     if(text == null) {
       throw new RoboCopException("getText: Text is null for view "+id);
     }
     return text.toString();
   }
 
-  @Override
+  private boolean displayed;
+
   public boolean isDisplayed() {
-    // TODO Auto-generated method stub
-    return false;
+    final SynchronousQueue syncQueue = new SynchronousQueue();
+    displayed = false;
+    mActivity.runOnUiThread(
+        new Runnable() {
+          public void run() {
+            View view = (View)mActivity.findViewById(id);
+            if(view != null) {
+              displayed = true;
+            }
+            syncQueue.offer(new Object());
+          }
+        });
+    try {
+      syncQueue.take();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    return displayed;
   }
+
 }
--- a/content/base/public/nsISelectionController.idl
+++ b/content/base/public/nsISelectionController.idl
@@ -46,17 +46,17 @@
 typedef short SelectionType;
 typedef short SelectionRegion;
 %}
 
 interface nsIDOMNode;
 interface nsISelection;
 interface nsISelectionDisplay;
 
-[scriptable, uuid(e0dd9365-470b-4ee8-b644-54add1c4c73f)]
+[scriptable, uuid(cf30315f-b65d-44c3-8c57-557e36d18fd2)]
 interface nsISelectionController : nsISelectionDisplay
 {
    const short SELECTION_NONE=0;
    const short SELECTION_NORMAL=1;
    const short SELECTION_SPELLCHECK=2;
    const short SELECTION_IME_RAWINPUT=4;
    const short SELECTION_IME_SELECTEDRAWTEXT=8;
    const short SELECTION_IME_CONVERTEDTEXT=16;
@@ -253,20 +253,21 @@ interface nsISelectionController : nsISe
    */
     void scrollPage(in boolean forward);
 
   /** ScrolLine will scroll line up or down dependent on the boolean
    *  @param aForward scroll forward or backwards in selection
    */
 	  void scrollLine(in boolean forward);
 
-  /** ScrolHorizontal will scroll left or right dependent on the boolean
-   *  @param aLeft if true will scroll left. if not will scroll right.
+  /** ScrollCharacter will scroll right or left dependent on the boolean
+   *  @param aRight if true will scroll right. if not will scroll left.
    */
-	  void scrollHorizontal(in boolean left);
+    void scrollCharacter(in boolean right);
+
   /** SelectAll will select the whole page
    */
     void selectAll();
 
   /** CheckVisibility will return true if textnode and offsets are actually rendered 
    *  in the current precontext.
    *  @param aNode textNode to test
    *  @param aStartOffset  offset in dom to first char of textnode to test
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8902,35 +8902,37 @@ nsDocument::RequestFullScreen(Element* a
   // document, as required by the spec.
   for (PRUint32 i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
 
   // Remember this is the requesting full-screen document.
   sFullScreenDoc = do_GetWeakReference(static_cast<nsIDocument*>(this));
 
-  // Make the window full-screen. Note we must make the state changes above
-  // before making the window full-screen, as then the document reports as
-  // being in full-screen mode when the chrome "fullscreen" event fires,
-  // enabling chrome to distinguish between browser and dom full-screen
-  // modes. Also note that nsGlobalWindow::SetFullScreen() (which
-  // SetWindowFullScreen() calls) proxies to the root window in its hierarchy,
-  // and does not operate on the a per-nsIDOMWindow basis.
-  SetWindowFullScreen(this, true);
-
 #ifdef DEBUG
+  // Note assertions must run before SetWindowFullScreen() as that does
+  // synchronous event dispatch which can run script which exits full-screen!
   NS_ASSERTION(GetFullScreenElement() == aElement,
                "Full-screen element should be the requested element!");
   NS_ASSERTION(IsFullScreenDoc(), "Should be full-screen doc");
   nsCOMPtr<nsIDOMHTMLElement> fse;
   GetMozFullScreenElement(getter_AddRefs(fse));
   nsCOMPtr<nsIContent> c(do_QueryInterface(fse));
   NS_ASSERTION(c->AsElement() == aElement,
     "GetMozFullScreenElement should match GetFullScreenElement()");
 #endif
+
+  // Make the window full-screen. Note we must make the state changes above
+  // before making the window full-screen, as then the document reports as
+  // being in full-screen mode when the chrome "fullscreen" event fires,
+  // enabling chrome to distinguish between browser and dom full-screen
+  // modes. Also note that nsGlobalWindow::SetFullScreen() (which
+  // SetWindowFullScreen() calls) proxies to the root window in its hierarchy,
+  // and does not operate on the a per-nsIDOMWindow basis.
+  SetWindowFullScreen(this, true);
 }
 
 NS_IMETHODIMP
 nsDocument::GetMozFullScreenElement(nsIDOMHTMLElement **aFullScreenElement)
 {
   NS_ENSURE_ARG_POINTER(aFullScreenElement);
   *aFullScreenElement = nsnull;
   if (IsFullScreenDoc()) {
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -154,20 +154,21 @@ nsImageLoadingContent::~nsImageLoadingCo
     }                                                                    \
   PR_END_MACRO
 
 
 /*
  * imgIContainerObserver impl
  */
 NS_IMETHODIMP
-nsImageLoadingContent::FrameChanged(imgIContainer* aContainer,
+nsImageLoadingContent::FrameChanged(imgIRequest* aRequest,
+                                    imgIContainer* aContainer,
                                     const nsIntRect* aDirtyRect)
 {
-  LOOP_OVER_OBSERVERS(FrameChanged(aContainer, aDirtyRect));
+  LOOP_OVER_OBSERVERS(FrameChanged(aRequest, aContainer, aDirtyRect));
   return NS_OK;
 }
             
 /*
  * imgIDecoderObserver impl
  */
 NS_IMETHODIMP
 nsImageLoadingContent::OnStartRequest(imgIRequest* aRequest)
--- a/content/base/src/nsStubImageDecoderObserver.cpp
+++ b/content/base/src/nsStubImageDecoderObserver.cpp
@@ -108,13 +108,14 @@ nsStubImageDecoderObserver::OnDiscard(im
 
 NS_IMETHODIMP
 nsStubImageDecoderObserver::OnImageIsAnimated(imgIRequest *aRequest)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsStubImageDecoderObserver::FrameChanged(imgIContainer *aContainer,
+nsStubImageDecoderObserver::FrameChanged(imgIRequest* aRequest,
+                                         imgIContainer *aContainer,
                                          const nsIntRect *aDirtyRect)
 {
     return NS_OK;
 }
--- a/content/base/src/nsWebSocket.cpp
+++ b/content/base/src/nsWebSocket.cpp
@@ -1563,16 +1563,17 @@ NS_IMETHODIMP
 nsWebSocket::Cancel(nsresult aStatus)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mDisconnected)
     return NS_OK;
 
   ConsoleError();
+  mClientReasonCode = nsIWebSocketChannel::CLOSE_GOING_AWAY;
   return CloseConnection();
 }
 
 NS_IMETHODIMP
 nsWebSocket::Suspend()
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -92,17 +92,16 @@
 
 #include "imgIEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxASurface.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
-#include "gfxTextRunCache.h"
 #include "gfxBlur.h"
 #include "gfxUtils.h"
 
 #include "nsFrameManager.h"
 #include "nsFrameLoader.h"
 #include "nsBidi.h"
 #include "nsBidiPresUtils.h"
 #include "Layers.h"
@@ -2732,22 +2731,21 @@ nsCanvasRenderingContext2D::MeasureText(
 
 /**
  * Used for nsBidiPresUtils::ProcessText
  */
 struct NS_STACK_CLASS nsCanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
 {
     virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
     {
-        mTextRun = gfxTextRunCache::MakeTextRun(text,
-                                                length,
-                                                mFontgrp,
-                                                mThebes,
-                                                mAppUnitsPerDevPixel,
-                                                direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
+        mTextRun = mFontgrp->MakeTextRun(text,
+                                         length,
+                                         mThebes,
+                                         mAppUnitsPerDevPixel,
+                                         direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
     }
 
     virtual nscoord GetWidth()
     {
         gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
                                                                    mTextRun->GetLength(),
                                                                    mDoMeasureBoundingBox ?
                                                                        gfxFont::TIGHT_INK_EXTENTS :
@@ -2806,17 +2804,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces
                            point,
                            0,
                            mTextRun->GetLength(),
                            nsnull,
                            nsnull);
     }
 
     // current text run
-    gfxTextRunCache::AutoTextRun mTextRun;
+    nsAutoPtr<gfxTextRun> mTextRun;
 
     // pointer to the context, may not be the canvas's context
     // if an intermediate surface is being used
     gfxContext* mThebes;
 
     // position of the left side of the string, alphabetic baseline
     gfxPoint mPt;
 
@@ -3150,18 +3148,18 @@ gfxTextRun*
 nsCanvasRenderingContext2D::MakeTextRun(const PRUnichar* aText,
                                         PRUint32         aLength,
                                         PRUint32         aAppUnitsPerDevUnit,
                                         PRUint32         aFlags)
 {
     gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
     if (!currentFontStyle)
         return nsnull;
-    return gfxTextRunCache::MakeTextRun(aText, aLength, currentFontStyle,
-                                        mThebes, aAppUnitsPerDevUnit, aFlags);
+    return currentFontStyle->MakeTextRun(aText, aLength,
+                                         mThebes, aAppUnitsPerDevUnit, aFlags);
 }
 
 
 //
 // line caps/joins
 //
 NS_IMETHODIMP
 nsCanvasRenderingContext2D::SetLineWidth(float width)
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -89,17 +89,16 @@
 
 #include "imgIEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxASurface.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
-#include "gfxTextRunCache.h"
 #include "gfxBlur.h"
 #include "gfxUtils.h"
 
 #include "nsFrameManager.h"
 #include "nsFrameLoader.h"
 #include "nsBidi.h"
 #include "nsBidiPresUtils.h"
 #include "Layers.h"
@@ -2937,22 +2936,21 @@ nsCanvasRenderingContext2DAzure::Measure
  * Used for nsBidiPresUtils::ProcessText
  */
 struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiProcessor
 {
   typedef nsCanvasRenderingContext2DAzure::ContextState ContextState;
 
   virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
   {
-    mTextRun = gfxTextRunCache::MakeTextRun(text,
-                                            length,
-                                            mFontgrp,
-                                            mThebes,
-                                            mAppUnitsPerDevPixel,
-                                            direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
+    mTextRun = mFontgrp->MakeTextRun(text,
+                                     length,
+                                     mThebes,
+                                     mAppUnitsPerDevPixel,
+                                     direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
   }
 
   virtual nscoord GetWidth()
   {
     gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
                                                                mTextRun->GetLength(),
                                                                mDoMeasureBoundingBox ?
                                                                  gfxFont::TIGHT_INK_EXTENTS :
@@ -3091,17 +3089,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces
                                 state.dashOffset),
                   DrawOptions(state.globalAlpha, mCtx->UsedOperation()));
 
       }
     }
   }
 
   // current text run
-  gfxTextRunCache::AutoTextRun mTextRun;
+  nsAutoPtr<gfxTextRun> mTextRun;
 
   // pointer to a screen reference context used to measure text and such
   nsRefPtr<gfxContext> mThebes;
 
   // Pointer to the draw target we should fill our text to
   nsCanvasRenderingContext2DAzure *mCtx;
 
   // position of the left side of the string, alphabetic baseline
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -297,16 +297,21 @@ void nsHTMLMediaElement::ReportLoadError
                                   aParamCount);
 }
 
 
 NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   nsContentUtils::UnregisterShutdownObserver(this);
 
+  if (!mElement) {
+    // We've been notified by the shutdown observer, and are shutting down.
+    return NS_BINDING_ABORTED;
+  }
+
   // The element is only needed until we've had a chance to call
   // InitializeDecoderForChannel. So make sure mElement is cleared here.
   nsRefPtr<nsHTMLMediaElement> element;
   element.swap(mElement);
 
   if (mLoadID != element->GetCurrentLoadID()) {
     // The channel has been cancelled before we had a chance to create
     // a decoder. Abort, don't dispatch an "error" event, as the new load
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -209,17 +209,17 @@ public:
   NS_IMETHOD WordExtendForDelete(bool aForward);
   NS_IMETHOD LineMove(bool aForward, bool aExtend);
   NS_IMETHOD IntraLineMove(bool aForward, bool aExtend);
   NS_IMETHOD PageMove(bool aForward, bool aExtend);
   NS_IMETHOD CompleteScroll(bool aForward);
   NS_IMETHOD CompleteMove(bool aForward, bool aExtend);
   NS_IMETHOD ScrollPage(bool aForward);
   NS_IMETHOD ScrollLine(bool aForward);
-  NS_IMETHOD ScrollHorizontal(bool aLeft);
+  NS_IMETHOD ScrollCharacter(bool aRight);
   NS_IMETHOD SelectAll(void);
   NS_IMETHOD CheckVisibility(nsIDOMNode *node, PRInt16 startOffset, PRInt16 EndOffset, bool *_retval);
 
 private:
   nsRefPtr<nsFrameSelection> mFrameSelection;
   nsCOMPtr<nsIContent>       mLimiter;
   nsIScrollableFrame        *mScrollFrame;
   nsWeakPtr mPresShellWeak;
@@ -568,22 +568,22 @@ nsTextInputSelectionImpl::ScrollLine(boo
 
   mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                          nsIScrollableFrame::LINES,
                          nsIScrollableFrame::SMOOTH);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsTextInputSelectionImpl::ScrollHorizontal(bool aLeft)
+nsTextInputSelectionImpl::ScrollCharacter(bool aRight)
 {
   if (!mScrollFrame)
     return NS_ERROR_NOT_INITIALIZED;
 
-  mScrollFrame->ScrollBy(nsIntPoint(aLeft ? -1 : 1, 0),
+  mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0),
                          nsIScrollableFrame::LINES,
                          nsIScrollableFrame::SMOOTH);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputSelectionImpl::SelectAll()
 {
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -268,17 +268,17 @@ nsresult nsBuiltinDecoder::Play()
   ChangeState(PLAY_STATE_PLAYING);
   return NS_OK;
 }
 
 /**
  * Returns true if aValue is inside a range of aRanges, and put the range
  * index in aIntervalIndex if it is not null.
  * If aValue is not inside a range, false is returned, and aIntervalIndex, if
- * not null, is set to the index of the range which ends immediatly before aValue
+ * not null, is set to the index of the range which ends immediately before aValue
  * (and can be -1 if aValue is before aRanges.Start(0)).
  */
 static bool IsInRanges(nsTimeRanges& aRanges, double aValue, PRInt32& aIntervalIndex) {
   PRUint32 length;
   aRanges.GetLength(&length);
   for (PRUint32 i = 0; i < length; i++) {
     double start, end;
     aRanges.Start(i, &start);
@@ -310,40 +310,42 @@ nsresult nsBuiltinDecoder::Seek(double a
   NS_ENSURE_SUCCESS(res, NS_OK);
 
   seekable.GetLength(&length);
   if (!length) {
     return NS_OK;
   }
 
   // If the position we want to seek to is not in a seekable range, we seek
-  // to the closest position in the seekable ranges instead . If two positions
-  // are equaly close, we seek to the closest position from the currentTime.
+  // to the closest position in the seekable ranges instead. If two positions
+  // are equally close, we seek to the closest position from the currentTime.
   // See seeking spec, point 7 :
-  // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#seeking
+  // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
   PRInt32 range = 0;
   if (!IsInRanges(seekable, aTime, range)) {
     if (range != -1) {
-      double leftBound, rightBound;
-      res = seekable.End(range, &leftBound);
-      NS_ENSURE_SUCCESS(res, NS_OK);
-      double distanceLeft = NS_ABS(leftBound - aTime);
-
-      double distanceRight = -1;
       if (range + 1 < length) {
-        res = seekable.Start(range+1, &rightBound);
+        double leftBound, rightBound;
+        res = seekable.End(range, &leftBound);
+        NS_ENSURE_SUCCESS(res, NS_OK);
+        res = seekable.Start(range + 1, &rightBound);
         NS_ENSURE_SUCCESS(res, NS_OK);
-        distanceRight = NS_ABS(rightBound - aTime);
+        double distanceLeft = NS_ABS(leftBound - aTime);
+        double distanceRight = NS_ABS(rightBound - aTime);
+        if (distanceLeft == distanceRight) {
+          distanceLeft = NS_ABS(leftBound - mCurrentTime);
+          distanceRight = NS_ABS(rightBound - mCurrentTime);
+        } 
+        aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
+      } else {
+        // Seek target is after the end last range in seekable data.
+        // Clamp the seek target to the end of the last seekable range.
+        res = seekable.End(length - 1, &aTime);
+        NS_ENSURE_SUCCESS(res, NS_OK);
       }
-
-      if (distanceLeft == distanceRight) {
-        distanceLeft = NS_ABS(leftBound - mCurrentTime);
-        distanceRight = NS_ABS(rightBound - mCurrentTime);
-      } 
-      aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
     } else {
       // aTime is before the first range in |seekable|, the closest point we can
       // seek to is the start of the first range.
       seekable.Start(0, &aTime);
     }
   }
 
   mRequestedSeekTime = aTime;
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -1119,16 +1119,43 @@ void nsBuiltinDecoderStateMachine::Reset
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   mVideoFrameEndTime = -1;
   mAudioStartTime = -1;
   mAudioEndTime = -1;
   mAudioCompleted = false;
 }
 
+void nsBuiltinDecoderStateMachine::NotifyDataArrived(const char* aBuffer,
+                                                     PRUint32 aLength,
+                                                     PRUint32 aOffset)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
+
+  // While playing an unseekable stream of unknown duration, mEndTime is
+  // updated (in AdvanceFrame()) as we play. But if data is being downloaded
+  // faster than played, mEndTime won't reflect the end of playable data
+  // since we haven't played the frame at the end of buffered data. So update
+  // mEndTime here as new data is downloaded to prevent such a lag.
+  nsTimeRanges buffered;
+  if (mDecoder->IsInfinite() &&
+      NS_SUCCEEDED(mDecoder->GetBuffered(&buffered)))
+  {
+    PRUint32 length = 0;
+    buffered.GetLength(&length);
+    if (length) {
+      double end = 0;
+      buffered.End(length - 1, &end);
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      mEndTime = NS_MAX<PRInt64>(mEndTime, end * USECS_PER_S);
+    }
+  }
+}
+
 void nsBuiltinDecoderStateMachine::Seek(double aTime)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   // nsBuiltinDecoder::mPlayState should be SEEKING while we seek, and
   // in that case nsBuiltinDecoder shouldn't be calling us.
   NS_ASSERTION(mState != DECODER_STATE_SEEKING,
                "We shouldn't already be seeking");
@@ -1500,17 +1527,21 @@ void nsBuiltinDecoderStateMachine::Decod
   LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n",
       mDecoder.get(), mCurrentFrameTime));
 
   // Change state to DECODING or COMPLETED now. SeekingStopped will
   // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
   // if we need to seek again.
 
   nsCOMPtr<nsIRunnable> stopEvent;
-  if (GetMediaTime() == mEndTime) {
+  bool isLiveStream = mDecoder->GetStream()->GetLength() == -1;
+  if (GetMediaTime() == mEndTime && !isLiveStream) {
+    // Seeked to end of media, move to COMPLETED state. Note we don't do
+    // this if we're playing a live stream, since the end of media will advance
+    // once we download more data!
     LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
                         mDecoder.get(), seekTime));
     stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd);
     mState = DECODER_STATE_COMPLETED;
   } else {
     LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING",
                         mDecoder.get(), seekTime));
     stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStopped);
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -218,20 +218,17 @@ public:
 
   PRInt64 AudioQueueMemoryInUse() {
     if (mReader) {
       return mReader->AudioQueueMemoryInUse();
     }
     return 0;
   }
 
-  void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
-    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
-  }
+  void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset);
 
   PRInt64 GetEndMediaTime() const {
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
     return mEndTime;
   }
 
   bool IsSeekable() {
     mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
--- a/content/svg/content/src/nsSVGFilters.cpp
+++ b/content/svg/content/src/nsSVGFilters.cpp
@@ -5737,21 +5737,22 @@ nsSVGFEImageElement::OnStopDecode(imgIRe
 {
   nsresult rv =
     nsImageLoadingContent::OnStopDecode(aRequest, status, statusArg);
   Invalidate();
   return rv;
 }
 
 NS_IMETHODIMP
-nsSVGFEImageElement::FrameChanged(imgIContainer *aContainer,
+nsSVGFEImageElement::FrameChanged(imgIRequest* aRequest,
+                                  imgIContainer *aContainer,
                                   const nsIntRect *aDirtyRect)
 {
   nsresult rv =
-    nsImageLoadingContent::FrameChanged(aContainer, aDirtyRect);
+    nsImageLoadingContent::FrameChanged(aRequest, aContainer, aDirtyRect);
   Invalidate();
   return rv;
 }
 
 NS_IMETHODIMP
 nsSVGFEImageElement::OnStartContainer(imgIRequest *aRequest,
                                       imgIContainer *aContainer)
 {
--- a/content/svg/content/src/nsSVGFilters.h
+++ b/content/svg/content/src/nsSVGFilters.h
@@ -293,17 +293,18 @@ public:
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers);
   virtual nsEventStates IntrinsicState() const;
 
   // imgIDecoderObserver
   NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
                           const PRUnichar *statusArg);
   // imgIContainerObserver
-  NS_IMETHOD FrameChanged(imgIContainer *aContainer,
+  NS_IMETHOD FrameChanged(imgIRequest* aRequest,
+                          imgIContainer *aContainer,
                           const nsIntRect *aDirtyRect);
   // imgIContainerObserver
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest,
                               imgIContainer *aContainer);
 
   void MaybeLoadSVGImage();
 
   virtual nsXPCClassInfo* GetClassInfo();
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1614,16 +1614,65 @@ nsDOMWindowUtils::GetLayerManagerType(ns
   if (!mgr)
     return NS_ERROR_FAILURE;
 
   mgr->GetBackendName(aType);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::StartFrameTimeRecording()
+{
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget)
+    return NS_ERROR_FAILURE;
+
+  LayerManager *mgr = widget->GetLayerManager();
+  if (!mgr)
+    return NS_ERROR_FAILURE;
+
+  mgr->StartFrameTimeRecording();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StopFrameTimeRecording(PRUint32 *frameCount NS_OUTPARAM, float **frames NS_OUTPARAM)
+{
+  NS_ENSURE_ARG_POINTER(frameCount);
+  NS_ENSURE_ARG_POINTER(frames);
+
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget)
+    return NS_ERROR_FAILURE;
+
+  LayerManager *mgr = widget->GetLayerManager();
+  if (!mgr)
+    return NS_ERROR_FAILURE;
+
+  nsTArray<float> frameTimes = mgr->StopFrameTimeRecording();
+
+  *frames = nsnull;
+  *frameCount = frameTimes.Length();
+
+  if (*frameCount != 0) {
+    *frames = (float*)nsMemory::Alloc(*frameCount * sizeof(float*));
+    if (!*frames)
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    /* copy over the frame times into the array we just allocated */
+    for (PRUint32 i = 0; i < *frameCount; i++) {
+      (*frames)[i] = frameTimes[i];
+    }
+  }
+
+  return NS_OK;
+}
+
 static bool
 ComputeAnimationValue(nsCSSProperty aProperty,
                       Element* aElement,
                       const nsAString& aInput,
                       nsStyleAnimation::Value& aOutput)
 {
 
   if (!nsStyleAnimation::ComputeValue(aProperty, aElement, aInput,
--- a/dom/base/nsGlobalWindowCommands.cpp
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -40,16 +40,17 @@
 #include "nsGlobalWindowCommands.h"
 
 #include "nsIComponentManager.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsCRT.h"
 #include "nsString.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Util.h"
 
 #include "nsIControllerCommandTable.h"
 #include "nsICommandParams.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsIPresShell.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
@@ -71,16 +72,20 @@ const char * const sSelectNoneString = "
 const char * const sCopyImageLocationString = "cmd_copyImageLocation";
 const char * const sCopyImageContentsString = "cmd_copyImageContents";
 const char * const sCopyImageString = "cmd_copyImage";
 
 const char * const sScrollTopString = "cmd_scrollTop";
 const char * const sScrollBottomString = "cmd_scrollBottom";
 const char * const sScrollPageUpString = "cmd_scrollPageUp";
 const char * const sScrollPageDownString = "cmd_scrollPageDown";
+const char * const sScrollLineUpString = "cmd_scrollLineUp";
+const char * const sScrollLineDownString = "cmd_scrollLineDown";
+const char * const sScrollLeftString = "cmd_scrollLeft";
+const char * const sScrollRightString = "cmd_scrollRight";
 const char * const sMoveTopString = "cmd_moveTop";
 const char * const sMoveBottomString = "cmd_moveBottom";
 const char * const sMovePageUpString = "cmd_movePageUp";
 const char * const sMovePageDownString = "cmd_movePageDown";
 const char * const sLinePreviousString = "cmd_linePrevious";
 const char * const sLineNextString = "cmd_lineNext";
 const char * const sCharPreviousString = "cmd_charPrevious";
 const char * const sCharNextString = "cmd_charNext";
@@ -116,50 +121,44 @@ const char * const sSelectBottomString =
 #endif
 
 // a base class for selection-related commands, for code sharing
 class nsSelectionCommandsBase : public nsIControllerCommand
 {
 public:
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSICONTROLLERCOMMAND
+  NS_IMETHOD IsCommandEnabled(const char * aCommandName, nsISupports *aCommandContext, bool *_retval NS_OUTPARAM);
+  NS_IMETHOD GetCommandStateParams(const char * aCommandName, nsICommandParams *aParams, nsISupports *aCommandContext);
+  NS_IMETHOD DoCommandParams(const char * aCommandName, nsICommandParams *aParams, nsISupports *aCommandContext);
 
 protected:
 
-  // subclasses override DoSelectCommand
-  virtual nsresult DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow) = 0;
-
-  static nsresult  GetPresShellFromWindow(nsIDOMWindow *aWindow, nsIPresShell **aPresShell);
-  static nsresult  GetSelectionControllerFromWindow(nsIDOMWindow *aWindow, nsISelectionController **aSelCon);
+  static nsresult  GetPresShellFromWindow(nsPIDOMWindow *aWindow, nsIPresShell **aPresShell);
+  static nsresult  GetSelectionControllerFromWindow(nsPIDOMWindow *aWindow, nsISelectionController **aSelCon);
 
   // no member variables, please, we're stateless!
 };
 
 // this class implements commands whose behavior depends on the 'browse with caret' setting
 class nsSelectMoveScrollCommand : public nsSelectionCommandsBase
 {
-protected:
+public:
 
-  virtual nsresult DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow);
-  
-  nsresult    DoCommandBrowseWithCaretOn(const char *aCommandName,
-                                         nsIDOMWindow *aWindow,
-                                         nsISelectionController *aSelectionController);
-  nsresult    DoCommandBrowseWithCaretOff(const char *aCommandName, nsISelectionController *aSelectionController);
+  NS_IMETHOD DoCommand(const char * aCommandName, nsISupports *aCommandContext);
 
   // no member variables, please, we're stateless!
 };
 
 // this class implements other selection commands
 class nsSelectCommand : public nsSelectionCommandsBase
 {
-protected:
+public:
 
-  virtual nsresult DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow);
+  NS_IMETHOD DoCommand(const char * aCommandName, nsISupports *aCommandContext);
 
   // no member variables, please, we're stateless!
 };
 
 #if 0
 #pragma mark -
 #endif
 
@@ -182,223 +181,147 @@ nsSelectionCommandsBase::IsCommandEnable
 NS_IMETHODIMP
 nsSelectionCommandsBase::GetCommandStateParams(const char *aCommandName,
                                             nsICommandParams *aParams, nsISupports *aCommandContext)
 {
   // XXX we should probably return the enabled state
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-NS_IMETHODIMP
-nsSelectionCommandsBase::DoCommand(const char *aCommandName, nsISupports *aCommandContext)
-{
-  nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(aCommandContext);
-  NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);       
-
-  return DoSelectCommand(aCommandName, window);
-}
-
 /* void doCommandParams (in string aCommandName, in nsICommandParams aParams, in nsISupports aCommandContext); */
 NS_IMETHODIMP
 nsSelectionCommandsBase::DoCommandParams(const char *aCommandName,
                                        nsICommandParams *aParams, nsISupports *aCommandContext)
 {
   return DoCommand(aCommandName, aCommandContext);
 }
 
 // protected methods
 
 nsresult
-nsSelectionCommandsBase::GetPresShellFromWindow(nsIDOMWindow *aWindow, nsIPresShell **aPresShell)
+nsSelectionCommandsBase::GetPresShellFromWindow(nsPIDOMWindow *aWindow, nsIPresShell **aPresShell)
 {
   *aPresShell = nsnull;
+  NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
 
-  nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aWindow));
-  NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
-
-  nsIDocShell *docShell = win->GetDocShell();
+  nsIDocShell *docShell = aWindow->GetDocShell();
   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
 
   return docShell->GetPresShell(aPresShell);
 }
 
 nsresult
-nsSelectionCommandsBase::GetSelectionControllerFromWindow(nsIDOMWindow *aWindow, nsISelectionController **aSelCon)
+nsSelectionCommandsBase::GetSelectionControllerFromWindow(nsPIDOMWindow *aWindow, nsISelectionController **aSelCon)
 {
   *aSelCon = nsnull;
 
   nsCOMPtr<nsIPresShell> presShell;
   GetPresShellFromWindow(aWindow, getter_AddRefs(presShell));
   if (presShell)
     return CallQueryInterface(presShell, aSelCon);
 
   return NS_ERROR_FAILURE;
 }
 
 #if 0
 #pragma mark -
 #endif
 
+static const struct BrowseCommand {
+  const char *reverse, *forward;
+  nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
+  nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool);
+} browseCommands[] = {
+ { sScrollTopString, sScrollBottomString,
+   &nsISelectionController::CompleteScroll },
+ { sScrollPageUpString, sScrollPageDownString,
+   &nsISelectionController::ScrollPage },
+ { sScrollLineUpString, sScrollLineDownString,
+   &nsISelectionController::ScrollLine },
+ { sScrollLeftString, sScrollRightString,
+   &nsISelectionController::ScrollCharacter },
+ { sMoveTopString, sMoveBottomString,
+   &nsISelectionController::CompleteScroll,
+   &nsISelectionController::CompleteMove },
+ { sMovePageUpString, sMovePageDownString,
+   &nsISelectionController::ScrollPage,
+   &nsISelectionController::PageMove },
+ { sLinePreviousString, sLineNextString,
+   &nsISelectionController::ScrollLine,
+   &nsISelectionController::LineMove },
+ { sWordPreviousString, sWordNextString,
+   &nsISelectionController::ScrollCharacter,
+   &nsISelectionController::WordMove },
+ { sCharPreviousString, sCharNextString,
+   &nsISelectionController::ScrollCharacter,
+   &nsISelectionController::CharacterMove },
+ { sBeginLineString, sEndLineString,
+   &nsISelectionController::CompleteScroll,
+   &nsISelectionController::IntraLineMove }
+};
+
 nsresult
-nsSelectMoveScrollCommand::DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow)
+nsSelectMoveScrollCommand::DoCommand(const char *aCommandName, nsISupports *aCommandContext)
 {
+  nsCOMPtr<nsPIDOMWindow> piWindow(do_QueryInterface(aCommandContext));
   nsCOMPtr<nsISelectionController> selCont;
-  GetSelectionControllerFromWindow(aWindow, getter_AddRefs(selCont));
+  GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
   NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);       
 
   // We allow the caret to be moved with arrow keys on any window for which
   // the caret is enabled. In particular, this includes caret-browsing mode
   // in non-chrome documents.
   bool caretOn = false;
   selCont->GetCaretEnabled(&caretOn);
   if (!caretOn) {
     caretOn = Preferences::GetBool("accessibility.browsewithcaret");
     if (caretOn) {
-      nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aWindow);
-      if (piWindow) {
-        nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(piWindow->GetDocShell());
-        if (dsti) {
-          PRInt32 itemType;
-          dsti->GetItemType(&itemType);
-          if (itemType == nsIDocShellTreeItem::typeChrome) {
-            caretOn = false;
-          }
+      nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(piWindow->GetDocShell());
+      if (dsti) {
+        PRInt32 itemType;
+        dsti->GetItemType(&itemType);
+        if (itemType == nsIDocShellTreeItem::typeChrome) {
+          caretOn = false;
         }
       }
     }
   }
 
-  if (caretOn) {
-    return DoCommandBrowseWithCaretOn(aCommandName, aWindow, selCont);
-  }
-
-  return DoCommandBrowseWithCaretOff(aCommandName, selCont);
-}
-
-nsresult
-nsSelectMoveScrollCommand::DoCommandBrowseWithCaretOn(const char *aCommandName,
-                  nsIDOMWindow *aWindow, nsISelectionController *aSelectionController)
-{
-  nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
-
-  // cmd_MoveTop/Bottom are used on Window/Unix. They move the caret
-  // in caret browsing mode.
-  if (!nsCRT::strcmp(aCommandName, sMoveTopString))
-    rv = aSelectionController->CompleteMove(false, false);
-  else if (!nsCRT::strcmp(aCommandName,sMoveBottomString))
-    rv = aSelectionController->CompleteMove(true, false);
-  // cmd_ScrollTop/Bottom are used on Mac. They do not move the
-  // caret in caret browsing mode.
-  else if (!nsCRT::strcmp(aCommandName, sScrollTopString))
-    rv = aSelectionController->CompleteScroll(false);
-  else if (!nsCRT::strcmp(aCommandName,sScrollBottomString))
-    rv = aSelectionController->CompleteScroll(true);
-  // cmd_MovePageUp/Down are used on Window/Unix. They move the caret
-  // in caret browsing mode.
-  else if (!nsCRT::strcmp(aCommandName, sMovePageUpString))
-    rv = aSelectionController->PageMove(false, false);
-  else if (!nsCRT::strcmp(aCommandName, sMovePageDownString))
-    rv = aSelectionController->PageMove(true, false);
-  // cmd_ScrollPageUp/Down are used on Mac, and for the spacebar on all platforms.
-  // They do not move the caret in caret browsing mode.
-  else if (!nsCRT::strcmp(aCommandName, sScrollPageUpString))
-    rv = aSelectionController->ScrollPage(false);
-  else if (!nsCRT::strcmp(aCommandName, sScrollPageDownString))
-    rv = aSelectionController->ScrollPage(true);
-  else if (!nsCRT::strcmp(aCommandName, sLinePreviousString))
-    rv = aSelectionController->LineMove(false, false);
-  else if (!nsCRT::strcmp(aCommandName, sLineNextString))
-    rv = aSelectionController->LineMove(true, false);
-  else if (!nsCRT::strcmp(aCommandName, sWordPreviousString))
-    rv = aSelectionController->WordMove(false, false);
-  else if (!nsCRT::strcmp(aCommandName, sWordNextString))
-    rv = aSelectionController->WordMove(true, false);
-  else if (!nsCRT::strcmp(aCommandName, sCharPreviousString))
-    rv = aSelectionController->CharacterMove(false, false);
-  else if (!nsCRT::strcmp(aCommandName, sCharNextString))
-    rv = aSelectionController->CharacterMove(true, false);
-  else if (!nsCRT::strcmp(aCommandName, sBeginLineString))
-    rv = aSelectionController->IntraLineMove(false, false);
-  else if (!nsCRT::strcmp(aCommandName, sEndLineString))
-    rv = aSelectionController->IntraLineMove(true, false);
-
-  if (NS_SUCCEEDED(rv))
-  {
-    // adjust the focus to the new caret position
-    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-    if (fm) {
-      nsCOMPtr<nsIDOMElement> result;
-      fm->MoveFocus(aWindow, nsnull, nsIFocusManager::MOVEFOCUS_CARET,
-                    nsIFocusManager::FLAG_NOSCROLL,
-                    getter_AddRefs(result));
+  for (int i = 0; i < mozilla::ArrayLength(browseCommands); i++) {
+    bool forward = !strcmp(aCommandName, browseCommands[i].forward);
+    if (forward || !strcmp(aCommandName, browseCommands[i].reverse)) {
+      if (caretOn && browseCommands[i].move &&
+          NS_SUCCEEDED((selCont->*(browseCommands[i].move))(forward, false))) {
+        // adjust the focus to the new caret position
+        nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+        if (fm) {
+          nsCOMPtr<nsIDOMElement> result;
+          fm->MoveFocus(piWindow, nsnull, nsIFocusManager::MOVEFOCUS_CARET,
+                        nsIFocusManager::FLAG_NOSCROLL,
+                        getter_AddRefs(result));
+        }
+        return NS_OK;
+      }
+      return (selCont->*(browseCommands[i].scroll))(forward);
     }
   }
-
-  return rv;
-}
-
-nsresult
-nsSelectMoveScrollCommand::DoCommandBrowseWithCaretOff(const char *aCommandName, nsISelectionController *aSelectionController)
-{
-  nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
-
-  // cmd_MoveTop/Bottom are used on Window/Unix. They move the caret
-  // in caret browsing mode.
-  if (!nsCRT::strcmp(aCommandName, sMoveTopString))
-    rv = aSelectionController->CompleteScroll(false);
-  else if (!nsCRT::strcmp(aCommandName,sMoveBottomString))
-    rv = aSelectionController->CompleteScroll(true);
-  // cmd_ScrollTop/Bottom are used on Mac. They do not move the
-  // caret in caret browsing mode.
-  else if (!nsCRT::strcmp(aCommandName, sScrollTopString))
-    rv = aSelectionController->CompleteScroll(false);
-  else if (!nsCRT::strcmp(aCommandName,sScrollBottomString))
-    rv = aSelectionController->CompleteScroll(true);
-
-  // cmd_MovePageUp/Down are used on Window/Unix. They move the caret
-  // in caret browsing mode.
-  else if (!nsCRT::strcmp(aCommandName, sMovePageUpString))
-    rv = aSelectionController->ScrollPage(false);
-  else if (!nsCRT::strcmp(aCommandName, sMovePageDownString))
-    rv = aSelectionController->ScrollPage(true);
-  // cmd_ScrollPageUp/Down are used on Mac. They do not move the
-  // caret in caret browsing mode.
-  else if (!nsCRT::strcmp(aCommandName, sScrollPageUpString))
-    rv = aSelectionController->ScrollPage(false);
-  else if (!nsCRT::strcmp(aCommandName, sScrollPageDownString))
-    rv = aSelectionController->ScrollPage(true);
-
-  else if (!nsCRT::strcmp(aCommandName, sLinePreviousString))
-    rv = aSelectionController->ScrollLine(false);
-  else if (!nsCRT::strcmp(aCommandName, sLineNextString))
-    rv = aSelectionController->ScrollLine(true);
-  else if (!nsCRT::strcmp(aCommandName, sCharPreviousString))
-    rv = aSelectionController->ScrollHorizontal(true);
-  else if (!nsCRT::strcmp(aCommandName, sCharNextString))
-    rv = aSelectionController->ScrollHorizontal(false);
-  // cmd_beginLine/endLine with caret browsing off
-  // will act as cmd_moveTop/Bottom
-  else if (!nsCRT::strcmp(aCommandName, sBeginLineString))
-    rv = aSelectionController->CompleteScroll(false);
-  else if (!nsCRT::strcmp(aCommandName, sEndLineString))
-    rv = aSelectionController->CompleteScroll(true);
-
-  return rv;
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 
 #if 0
 #pragma mark -
 #endif
 
 nsresult
-nsSelectCommand::DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow)
+nsSelectCommand::DoCommand(const char *aCommandName, nsISupports *aCommandContext)
 {
+  nsCOMPtr<nsPIDOMWindow> piWindow(do_QueryInterface(aCommandContext));
   nsCOMPtr<nsISelectionController> selCont;
-  GetSelectionControllerFromWindow(aWindow, getter_AddRefs(selCont));
+  GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
   NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);       
 
   nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
 
   // These commands are so the browser can use caret navigation key bindings -
   // Helps with accessibility - aaronl@netscape.com
   if (!nsCRT::strcmp(aCommandName, sSelectCharPreviousString))
     rv = selCont->CharacterMove(false, true);
@@ -963,26 +886,30 @@ nsWindowCommandRegistration::RegisterWin
 {
   nsresult rv;
 
   // XXX rework the macros to use a loop is possible, reducing code size
   
   // this set of commands is affected by the 'browse with caret' setting
   NS_REGISTER_FIRST_COMMAND(nsSelectMoveScrollCommand, sScrollTopString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollBottomString);
+  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageUpString);
+  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageDownString);
+  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLineUpString);
+  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLineDownString);
+  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLeftString);
+  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollRightString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMoveTopString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMoveBottomString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sWordPreviousString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sWordNextString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sBeginLineString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sEndLineString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMovePageUpString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMovePageDownString);
-  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageUpString);
-  NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageDownString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sLinePreviousString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sLineNextString);
   NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sCharPreviousString);
   NS_REGISTER_LAST_COMMAND(nsSelectMoveScrollCommand, sCharNextString);
 
   NS_REGISTER_FIRST_COMMAND(nsSelectCommand, sSelectCharPreviousString);
   NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectCharNextString);
   NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectWordPreviousString);
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -247,23 +247,16 @@ IndexedDatabaseManager::GetOrCreate()
     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     NS_ENSURE_TRUE(obs, nsnull);
 
     // We need this callback to know when to shut down all our threads.
     nsresult rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
                                    false);
     NS_ENSURE_SUCCESS(rv, nsnull);
 
-    // We don't really need this callback but we want the observer service to
-    // hold us alive until XPCOM shutdown. That way other consumers can continue
-    // to use this service until shutdown.
-    rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
-                          false);
-    NS_ENSURE_SUCCESS(rv, nsnull);
-
     // Make a lazy thread for any IO we need (like clearing or enumerating the
     // contents of indexedDB database directories).
     instance->mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
                                              LazyIdleThread::ManualShutdown);
 
     // We need one quota callback object to hand to SQLite.
     instance->mQuotaCallbackSingleton = new QuotaCallback();
 
@@ -1219,17 +1212,17 @@ IndexedDatabaseManager::ClearDatabasesFo
 
 NS_IMETHODIMP
 IndexedDatabaseManager::Observe(nsISupports* aSubject,
                                 const char* aTopic,
                                 const PRUnichar* aData)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     // Setting this flag prevents the service from being recreated and prevents
     // further databases from being created.
     if (PR_ATOMIC_SET(&gShutdown, 1)) {
       NS_ERROR("Shutdown more than once?!");
     }
 
     // Make sure to join with our IO thread.
     if (NS_FAILED(mIOThread->Shutdown())) {
@@ -1274,21 +1267,16 @@ IndexedDatabaseManager::Observe(nsISuppo
       for (PRUint32 index = 0; index < count; index++) {
         liveDatabases[index]->Invalidate();
       }
     }
 
     return NS_OK;
   }
 
-  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
-    // We're dying now.
-    return NS_OK;
-  }
-
   NS_NOTREACHED("Unknown topic!");
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable,
                               nsIRunnable)
 
 NS_IMETHODIMP
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.cpp
@@ -35,16 +35,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "Key.h"
 #include "nsIStreamBufferAccess.h"
 #include "jsdate.h"
+#include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsJSUtils.h"
 #include "xpcpublic.h"
 
 USING_INDEXEDDB_NAMESPACE
 
 /*
  Here's how we encode keys:
@@ -406,38 +407,34 @@ Key::EncodeNumber(double aFloat, PRUint8
   *(buffer++) = aType;
 
   Float64Union pun;
   pun.d = aFloat;
   PRUint64 number = pun.u & PR_UINT64(0x8000000000000000) ?
                     -pun.u :
                     (pun.u | PR_UINT64(0x8000000000000000));
 
-  *reinterpret_cast<PRUint64*>(buffer) = NS_SWAP64(number);
+  number = NS_SWAP64(number);
+  memcpy(buffer, &number, sizeof(number));
 }
 
 // static
 double
 Key::DecodeNumber(const unsigned char*& aPos, const unsigned char* aEnd)
 {
   NS_ASSERTION(*aPos % eMaxType == eFloat ||
                *aPos % eMaxType == eDate, "Don't call me!");
 
   ++aPos;
+
   PRUint64 number = 0;
-  for (PRInt32 n = 7; n >= 0; --n) {
-    number <<= 8;
-    if (aPos < aEnd) {
-      number |= *(aPos++);
-    }
-    else {
-      number <<= 8 * n;
-      break;
-    }
-  }
+  memcpy(&number, aPos, NS_MIN<size_t>(sizeof(number), aEnd - aPos));
+  number = NS_SWAP64(number);
+
+  aPos += sizeof(number);
 
   Float64Union pun;
   pun.u = number & PR_UINT64(0x8000000000000000) ?
           (number & ~PR_UINT64(0x8000000000000000)) :
           -number;
 
   return pun.d;
 }
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -64,17 +64,17 @@ interface nsIDOMHTMLCanvasElement;
 interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 
-[scriptable, uuid(c1fa9c82-acf2-4b27-8ca7-7d1864e606af)]
+[scriptable, uuid(15fcceb0-37ea-11e1-b86c-0800200c9a66)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -833,16 +833,19 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * What type of layer manager the widget associated with this window is
    * using. "Basic" is unaccelerated; other types are accelerated. Throws an
    * error if there is no widget associated with this window.
    */
   readonly attribute AString layerManagerType;
 
+  void startFrameTimeRecording();
+  void stopFrameTimeRecording([optional] out unsigned long frameCount,
+                              [retval, array, size_is(frameCount)] out float frameTime);
   /**
    * The DPI of the display
    */
   readonly attribute float displayDPI;
 
   /**
    * Return the outer window with the given ID, if any.  Can return null.
    */
--- a/dom/src/storage/nsDOMStoragePersistentDB.cpp
+++ b/dom/src/storage/nsDOMStoragePersistentDB.cpp
@@ -203,17 +203,17 @@ nsDOMStoragePersistentDB::Init(const nsS
     // delete the db and try opening again
     rv = storageFile->Remove(false);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = service->OpenDatabase(storageFile, getter_AddRefs(mConnection));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-        "PRAGMA temp_store = MEMORY"));
+        MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mozStorageTransaction transaction(mConnection, false);
 
   // Ensure Gecko 1.9.1 storage table
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
          "CREATE TABLE IF NOT EXISTS webappsstore2 ("
          "scope TEXT, "
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -147,16 +147,18 @@ const char* gStringChars[] = {
 PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gStringChars) == ID_COUNT);
 
 enum {
   PREF_strict = 0,
   PREF_werror,
   PREF_relimit,
   PREF_methodjit,
   PREF_methodjit_always,
+  PREF_typeinference,
+  PREF_jit_hardening,
   PREF_mem_max,
 
 #ifdef JS_GC_ZEAL
   PREF_gczeal,
 #endif
 
   PREF_COUNT
 };
@@ -164,16 +166,18 @@ enum {
 #define JS_OPTIONS_DOT_STR "javascript.options."
 
 const char* gPrefsToWatch[] = {
   JS_OPTIONS_DOT_STR "strict",
   JS_OPTIONS_DOT_STR "werror",
   JS_OPTIONS_DOT_STR "relimit",
   JS_OPTIONS_DOT_STR "methodjit.content",
   JS_OPTIONS_DOT_STR "methodjit_always",
+  JS_OPTIONS_DOT_STR "typeinference",
+  JS_OPTIONS_DOT_STR "jit_hardening",
   JS_OPTIONS_DOT_STR "mem.max"
 
 #ifdef JS_GC_ZEAL
   , PREF_WORKERS_GCZEAL
 #endif
 };
 
 PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gPrefsToWatch) == PREF_COUNT);
@@ -208,16 +212,25 @@ PrefCallback(const char* aPrefName, void
       newOptions |= JSOPTION_RELIMIT;
     }
     if (Preferences::GetBool(gPrefsToWatch[PREF_methodjit])) {
       newOptions |= JSOPTION_METHODJIT;
     }
     if (Preferences::GetBool(gPrefsToWatch[PREF_methodjit_always])) {
       newOptions |= JSOPTION_METHODJIT_ALWAYS;
     }
+    if (Preferences::GetBool(gPrefsToWatch[PREF_typeinference])) {
+      newOptions |= JSOPTION_TYPE_INFERENCE;
+    }
+
+    // This one is special, it's enabled by default and only needs to be unset.
+    if (!Preferences::GetBool(gPrefsToWatch[PREF_jit_hardening])) {
+      newOptions |= JSOPTION_SOFTEN;
+    }
+
     RuntimeService::SetDefaultJSContextOptions(newOptions);
     rts->UpdateAllWorkerJSContextOptions();
   }
 #ifdef JS_GC_ZEAL
   else if (!strcmp(aPrefName, gPrefsToWatch[PREF_gczeal])) {
     PRInt32 gczeal = Preferences::GetInt(gPrefsToWatch[PREF_gczeal]);
     RuntimeService::SetDefaultGCZeal(PRUint8(clamped(gczeal, 0, 3)));
     rts->UpdateAllWorkerGCZeal();
--- a/editor/libeditor/base/tests/test_selection_move_commands.xul
+++ b/editor/libeditor/base/tests/test_selection_move_commands.xul
@@ -118,16 +118,26 @@ function execTests() {
   var pageHeight = -root.getBoundingClientRect().top;
   ok(pageHeight > 0, "cmd_scrollPageDown works");
   ok(pageHeight <= 100, "cmd_scrollPageDown doesn't scroll too much");
   doCommand("cmd_scrollBottom");
   doCommand("cmd_scrollPageUp");
   yield;
   testScrollCommand("cmd_scrollPageUp", root.scrollHeight - 100 - pageHeight);
 
+  doCommand("cmd_scrollTop");
+  doCommand("cmd_scrollLineDown");
+  yield;
+  var lineHeight = -root.getBoundingClientRect().top;
+  ok(lineHeight > 0, "Can scroll by lines");
+  doCommand("cmd_scrollBottom");
+  doCommand("cmd_scrollLineUp");
+  yield;
+  testScrollCommand("cmd_scrollLineUp", root.scrollHeight - 100 - lineHeight);
+
   var runSelectionTests = function(selectWordNextNode, selectWordNextOffset) {
     testMoveCommand("cmd_moveBottom", body, 23);
     testMoveCommand("cmd_moveTop", node(0), 0);
     testSelectCommand("cmd_selectBottom", body, 23);
     doCommand("cmd_moveBottom");
     testSelectCommand("cmd_selectTop", node(0), 0);
 
     doCommand("cmd_moveTop");
--- a/editor/libeditor/text/nsTextEditRules.cpp
+++ b/editor/libeditor/text/nsTextEditRules.cpp
@@ -939,27 +939,23 @@ nsTextEditRules::WillUndo(nsISelection *
  */
 nsresult
 nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult)
 {
   nsresult res = aResult;  // if aResult is an error, we return it.
   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
   if (NS_SUCCEEDED(res)) 
   {
-    if (mBogusNode) {
-      mBogusNode = nsnull;
-    }
+    nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
+    NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
+    nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot);
+    if (node && mEditor->IsMozEditorBogusNode(node))
+      mBogusNode = node;
     else
-    {
-      nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
-      NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
-      nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot);
-      if (node && mEditor->IsMozEditorBogusNode(node))
-        mBogusNode = node;
-    }
+      mBogusNode = nsnull;
   }
   return res;
 }
 
 nsresult
 nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled)
 {
   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
@@ -972,40 +968,41 @@ nsTextEditRules::WillRedo(nsISelection *
 
 nsresult
 nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult)
 {
   nsresult res = aResult;  // if aResult is an error, we return it.
   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
   if (NS_SUCCEEDED(res)) 
   {
-    if (mBogusNode) {
-      mBogusNode = nsnull;
-    }
-    else
+    nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
+    NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
+    
+    nsCOMPtr<nsIDOMNodeList> nodeList;
+    res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
+                                        getter_AddRefs(nodeList));
+    NS_ENSURE_SUCCESS(res, res);
+    if (nodeList)
     {
-      nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
-      NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
+      PRUint32 len;
+      nodeList->GetLength(&len);
       
-      nsCOMPtr<nsIDOMNodeList> nodeList;
-      res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
-                                          getter_AddRefs(nodeList));
-      NS_ENSURE_SUCCESS(res, res);
-      if (nodeList)
-      {
-        PRUint32 len;
-        nodeList->GetLength(&len);
-        
-        if (len != 1) return NS_OK;  // only in the case of one br could there be the bogus node
-        nsCOMPtr<nsIDOMNode> node;
-        nodeList->Item(0, getter_AddRefs(node));
-        NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
-        if (mEditor->IsMozEditorBogusNode(node))
-          mBogusNode = node;
+      if (len != 1) {
+        // only in the case of one br could there be the bogus node
+        mBogusNode = nsnull;
+        return NS_OK;  
       }
+
+      nsCOMPtr<nsIDOMNode> node;
+      nodeList->Item(0, getter_AddRefs(node));
+      NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
+      if (mEditor->IsMozEditorBogusNode(node))
+        mBogusNode = node;
+      else
+        mBogusNode = nsnull;
     }
   }
   return res;
 }
 
 nsresult
 nsTextEditRules::WillOutputText(nsISelection *aSelection, 
                                 const nsAString  *aOutputFormat,
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -71,16 +71,17 @@ include $(topsrcdir)/config/rules.mk
 # on our editor, and the combinations depend on the system.
 ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 _TEST_FILES += \
 		test_texteditor_keyevent_handling.html \
 		$(NULL)
 endif
 
 _CHROME_TEST_FILES = \
+		test_bug471319.html \
 		test_bug483651.html \
 		test_bug636465.xul \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
 libs:: $(_CHROME_TEST_FILES)
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/text/tests/test_bug471319.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is Plaintext Editor Test code
+   -
+   - The Initial Developer of the Original Code is
+   - Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>.
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471319
+-->
+
+<head>
+  <title>Test for Bug 471319</title>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+
+<body onload="doTest();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471319">Mozilla Bug 471319</a>
+  <p id="display"></p>
+  <div id="content" style="display: none">
+  </div>
+
+  <pre id="test">
+    <script type="application/javascript;version=1.7">
+
+      /** Test for Bug 471319 **/
+    
+      SimpleTest.waitForExplicitFinish();
+
+      function doTest() {
+        let t1 = $("t1");
+        let editor = null;
+
+        // Test 1: Undo on an empty editor - the editor should not forget about
+        // the bogus node
+        t1.QueryInterface(Components.interfaces.nsIDOMNSEditableElement);
+        t1Editor = t1.editor;
+
+        // Did the editor recognize the new bogus node?
+        t1Editor.undo(1);
+        ok(!t1.value, "<br> still recognized as bogus node on undo");
+
+
+        // Test 2: Redo on an empty editor - the editor should not forget about
+        // the bogus node
+        let t2 = $("t2");
+        t2.QueryInterface(Components.interfaces.nsIDOMNSEditableElement);
+        t2Editor = t2.editor;
+
+        // Did the editor recognize the new bogus node?
+        t2Editor.redo(1);
+        ok(!t2.value, "<br> still recognized as bogus node on redo");
+
+
+        // Test 3: Undoing a batched transaction where both end points of the
+        // transaction are the bogus node - the bogus node should still be
+        // recognized as bogus
+        t1Editor.transactionManager.beginBatch();
+        t1.value = "mozilla";
+        t1.value = "";
+        t1Editor.transactionManager.endBatch();
+        t1Editor.undo(1);
+        ok(!t1.value,
+           "recreated <br> from undo transaction recognized as bogus");
+
+
+        // Test 4: Redoing a batched transaction where both end points of the
+        // transaction are the bogus node - the bogus node should still be
+        // recognized as bogus
+        t1Editor.redo(1);
+        ok(!t1.value,
+           "recreated <br> from redo transaction recognized as bogus");
+        SimpleTest.finish();
+     }
+   </script>
+  </pre>
+
+  <input type="text" id="t1" />
+  <input type="text" id="t2" />
+</body>
+</html>
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -556,16 +556,41 @@ PlanarYCbCrImage::CopyData(Data& aDest, 
            aData.mCrChannel + i * aData.mCbCrStride,
            aDest.mCbCrStride);
   }
 
   aDestSize = aData.mPicSize;
   return buffer;
 }
                          
+void
+LayerManager::StartFrameTimeRecording()
+{
+  mLastFrameTime = TimeStamp::Now();
+}
+
+void
+LayerManager::PostPresent()
+{
+  if (!mLastFrameTime.IsNull()) {
+    TimeStamp now = TimeStamp::Now();
+    mFrameTimes.AppendElement((now - mLastFrameTime).ToMilliseconds());
+    mLastFrameTime = now;
+  }
+}
+
+nsTArray<float>
+LayerManager::StopFrameTimeRecording()
+{
+  mLastFrameTime = TimeStamp();
+  nsTArray<float> result = mFrameTimes;
+  mFrameTimes.Clear();
+  return result;
+}
+
 
 
 #ifdef MOZ_LAYERS_HAVE_LOG
 
 static nsACString& PrintInfo(nsACString& aTo, ShadowLayer* aShadowLayer);
 
 void
 Layer::Dump(FILE* aFile, const char* aPrefix)
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -46,16 +46,17 @@
 #include "nsISupportsImpl.h"
 #include "nsAutoPtr.h"
 #include "gfx3DMatrix.h"
 #include "gfxColor.h"
 #include "gfxPattern.h"
 #include "nsTArray.h"
 
 #include "mozilla/gfx/2D.h"
+#include "mozilla/TimeStamp.h"
 
 #if defined(DEBUG) || defined(PR_LOGGING)
 #  include <stdio.h>            // FILE
 #  include "prlog.h"
 #  define MOZ_LAYERS_HAVE_LOG
 #  define MOZ_LAYERS_LOG(_args)                             \
   PR_LOG(LayerManager::GetLog(), PR_LOG_DEBUG, _args)
 #else
@@ -507,16 +508,21 @@ public:
    */
   void Log(const char* aPrefix="");
   /**
    * Log information about just this layer manager itself to the NSPR
    * log (if enabled for "Layers").
    */
   void LogSelf(const char* aPrefix="");
 
+  void StartFrameTimeRecording();
+  nsTArray<float> StopFrameTimeRecording();
+
+  void PostPresent();
+
   static bool IsLogEnabled();
   static PRLogModuleInfo* GetLog() { return sLog; }
 
   bool IsCompositingCheap(LayerManager::LayersBackend aBackend)
   { return LAYERS_BASIC != aBackend; }
 
   virtual bool IsCompositingCheap() { return true; }
 
@@ -527,16 +533,19 @@ protected:
   bool mSnapEffectiveTransforms;
 
   // Print interesting information about this into aTo.  Internally
   // used to implement Dump*() and Log*().
   virtual nsACString& PrintInfo(nsACString& aTo, const char* aPrefix);
 
   static void InitLog();
   static PRLogModuleInfo* sLog;
+private:
+  TimeStamp mLastFrameTime;
+  nsTArray<float> mFrameTimes;
 };
 
 class ThebesLayer;
 
 /**
  * A Layer represents anything that can be rendered onto a destination
  * surface.
  */
--- a/gfx/layers/d3d10/LayerManagerD3D10.cpp
+++ b/gfx/layers/d3d10/LayerManagerD3D10.cpp
@@ -773,16 +773,17 @@ LayerManagerD3D10::Render()
     ShadowLayerForwarder::EndTransaction(&replies);
     // We expect only 1 reply, but might get none if the parent
     // process crashed
 
     swap(mBackBuffer, mRemoteFrontBuffer);
   } else {
     mSwapChain->Present(0, 0);
   }
+  LayerManager::PostPresent();
 }
 
 void
 LayerManagerD3D10::PaintToTarget()
 {
   nsRefPtr<ID3D10Texture2D> backBuf;
   
   mSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (void**)backBuf.StartAssignment());
--- a/gfx/layers/d3d9/LayerManagerD3D9.cpp
+++ b/gfx/layers/d3d9/LayerManagerD3D9.cpp
@@ -349,16 +349,17 @@ LayerManagerD3D9::Render()
   device()->EndScene();
 
   if (!mTarget) {
     const nsIntRect *r;
     for (nsIntRegionRectIterator iter(mClippingRegion);
          (r = iter.Next()) != nsnull;) {
       mSwapChain->Present(*r);
     }
+    LayerManager::PostPresent();
   } else {
     PaintToTarget();
   }
 }
 
 void
 LayerManagerD3D9::SetupPipeline()
 {
--- a/gfx/layers/opengl/LayerManagerOGL.cpp
+++ b/gfx/layers/opengl/LayerManagerOGL.cpp
@@ -814,16 +814,17 @@ LayerManagerOGL::Render()
   }
 
   if (sDrawFPS) {
     mFPS.DrawFPS(mGLContext, GetCopy2DProgram());
   }
 
   if (mGLContext->IsDoubleBuffered()) {
     mGLContext->SwapBuffers();
+    LayerManager::PostPresent();
     mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
     return;
   }
 
   mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
 
   mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
 
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -36,48 +36,54 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsFontMetrics.h"
 #include "nsBoundingMetrics.h"
 #include "nsRenderingContext.h"
 #include "nsDeviceContext.h"
 #include "nsStyleConsts.h"
-#include "gfxTextRunCache.h"
 
 namespace {
 
-class AutoTextRun : public gfxTextRunCache::AutoTextRun {
+class AutoTextRun {
 public:
     AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC,
                 const char* aString, PRInt32 aLength)
-        : gfxTextRunCache::AutoTextRun(gfxTextRunCache::MakeTextRun(
+    {
+        mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
             reinterpret_cast<const PRUint8*>(aString), aLength,
-            aMetrics->GetThebesFontGroup(), aRC->ThebesContext(),
+            aRC->ThebesContext(),
             aMetrics->AppUnitsPerDevPixel(),
-            ComputeFlags(aMetrics)))
-    {}
+            ComputeFlags(aMetrics));
+    }
 
     AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC,
                 const PRUnichar* aString, PRInt32 aLength)
-        : gfxTextRunCache::AutoTextRun(gfxTextRunCache::MakeTextRun(
-            aString, aLength, aMetrics->GetThebesFontGroup(),
+    {
+        mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
+            aString, aLength,
             aRC->ThebesContext(),
             aMetrics->AppUnitsPerDevPixel(),
-            ComputeFlags(aMetrics)))
-    {}
+            ComputeFlags(aMetrics));
+    }
+
+    gfxTextRun *get() { return mTextRun; }
+    gfxTextRun *operator->() { return mTextRun; }
 
 private:
     static PRUint32 ComputeFlags(nsFontMetrics* aMetrics) {
         PRUint32 flags = 0;
         if (aMetrics->GetTextRunRTL()) {
             flags |= gfxTextRunFactory::TEXT_IS_RTL;
         }
         return flags;
     }
+
+    nsAutoPtr<gfxTextRun> mTextRun;
 };
 
 class StubPropertyProvider : public gfxTextRun::PropertyProvider {
 public:
     virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
                                       bool* aBreakBefore) {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
     }
--- a/gfx/tests/gfxFontSelectionTest.cpp
+++ b/gfx/tests/gfxFontSelectionTest.cpp
@@ -40,17 +40,16 @@
 #include "nsString.h"
 #include "nsDependentString.h"
 
 #include "mozilla/Preferences.h"
 
 #include "gfxContext.h"
 #include "gfxFont.h"
 #include "gfxPlatform.h"
-#include "gfxTextRunWordCache.h"
 
 #include "gfxFontTest.h"
 
 #if defined(XP_MACOSX)
 #include "gfxTestCocoaHelper.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
@@ -61,18 +60,16 @@ using namespace mozilla;
 
 enum {
     S_UTF8 = 0,
     S_ASCII = 1
 };
 
 class FrameTextRunCache;
 
-static gfxTextRunWordCache *gTextRunCache;
-
 struct LiteralArray {
     LiteralArray (unsigned long l1) {
         data.AppendElement(l1);
     }
     LiteralArray (unsigned long l1, unsigned long l2) {
         data.AppendElement(l1);
         data.AppendElement(l2);
     }
@@ -299,21 +296,21 @@ RunTest (TestEntry *test, gfxContext *ct
     PRUint32 flags = gfxTextRunFactory::TEXT_IS_PERSISTENT;
     if (test->isRTL) {
         flags |= gfxTextRunFactory::TEXT_IS_RTL;
     }
     PRUint32 length;
     if (test->stringType == S_ASCII) {
         flags |= gfxTextRunFactory::TEXT_IS_ASCII | gfxTextRunFactory::TEXT_IS_8BIT;
         length = strlen(test->string);
-        textRun = gfxTextRunWordCache::MakeTextRun(reinterpret_cast<const PRUint8*>(test->string), length, fontGroup, &params, flags);
+        textRun = fontGroup->MakeTextRun(reinterpret_cast<const PRUint8*>(test->string), length, &params, flags);
     } else {
         NS_ConvertUTF8toUTF16 str(nsDependentCString(test->string));
         length = str.Length();
-        textRun = gfxTextRunWordCache::MakeTextRun(str.get(), length, fontGroup, &params, flags);
+        textRun = fontGroup->MakeTextRun(str.get(), length, &params, flags);
     }
 
     gfxFontTestStore::NewStore();
     textRun->Draw(ctx, gfxPoint(0,0), 0, length, nsnull, nsnull);
     gfxFontTestStore *s = gfxFontTestStore::CurrentStore();
 
     gTextRunCache->RemoveTextRun(textRun);
 
@@ -343,18 +340,16 @@ main (int argc, char **argv) {
     nsresult rv = NS_InitXPCOM2(nsnull, nsnull, nsnull);
     if (NS_FAILED(rv))
         return -1;
 
     rv = gfxPlatform::Init();
     if (NS_FAILED(rv))
         return -1;
 
-    gTextRunCache = new gfxTextRunWordCache();
-
     // let's get all the xpcom goop out of the system
     fflush (stderr);
     fflush (stdout);
 
     // don't need to query, we might need to set up some prefs later
     if (0) {
         nsresult rv;
 
--- a/gfx/tests/gfxWordCacheTest.cpp
+++ b/gfx/tests/gfxWordCacheTest.cpp
@@ -43,18 +43,16 @@
 #include "prinrval.h"
 
 #include "gfxContext.h"
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 
 #include "gfxFontTest.h"
 
-#include "gfxTextRunWordCache.h"
-
 #if defined(XP_MACOSX)
 #include "gfxTestCocoaHelper.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
 #include "gtk/gtk.h"
 #endif
 
@@ -73,17 +71,16 @@ public:
  ~FrameTextRunCache() {
    AgeAllGenerations();
  }
 
  void RemoveFromCache(gfxTextRun* aTextRun) {
    if (aTextRun->GetExpirationState()->IsTracked()) {
      RemoveObject(aTextRun);
    }
-   gfxTextRunWordCache::RemoveTextRun(aTextRun);
  }
 
  // This gets called when the timeout has expired on a gfxTextRun
  virtual void NotifyExpired(gfxTextRun* aTextRun) {
    RemoveFromCache(aTextRun);
    delete aTextRun;
  }
 };
@@ -94,18 +91,17 @@ MakeTextRun(const PRUnichar *aText, PRUi
            PRUint32 aFlags)
 {
    nsAutoPtr<gfxTextRun> textRun;
    if (aLength == 0) {
        textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags);
    } else if (aLength == 1 && aText[0] == ' ') {
        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
    } else {
-       textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
-           aParams, aFlags);
+       textRun = aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
    }
    if (!textRun)
        return nsnull;
    nsresult rv = gTextRuns->AddObject(textRun);
    if (NS_FAILED(rv)) {
        gTextRuns->RemoveFromCache(textRun);
        return nsnull;
    }
--- a/gfx/thebes/Makefile.in
+++ b/gfx/thebes/Makefile.in
@@ -70,18 +70,16 @@ EXPORTS	= \
 	gfxPoint3D.h \
 	gfxPointH3D.h \
 	gfxQuad.h \
 	gfxQuaternion.h \
 	gfxRect.h \
 	gfxSkipChars.h \
 	gfxTeeSurface.h \
 	gfxTypes.h \
-	gfxTextRunCache.h \
-	gfxTextRunWordCache.h \
 	gfxUnicodeProperties.h \
 	gfxUtils.h \
 	gfxUserFontSet.h \
 	nsCoreAnimationSupport.h \
 	gfxSharedImageSurface.h \
 	$(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),android)
@@ -197,18 +195,16 @@ CPPSRCS	= \
 	gfxMatrix.cpp \
 	gfxPath.cpp \
 	gfxPattern.cpp \
 	gfxPlatform.cpp \
 	gfxPlatformFontList.cpp \
 	gfxRect.cpp \
 	gfxSkipChars.cpp \
 	gfxTeeSurface.cpp \
-	gfxTextRunCache.cpp \
-	gfxTextRunWordCache.cpp \
 	gfxUserFontSet.cpp \
 	gfxUtils.cpp \
 	gfxUnicodeProperties.cpp \
 	gfxScriptItemizer.cpp \
 	gfxHarfBuzzShaper.cpp \
 	gfxSharedImageSurface.cpp \
 	$(NULL)
 
--- a/gfx/thebes/gfxASurface.h
+++ b/gfx/thebes/gfxASurface.h
@@ -52,19 +52,34 @@ struct nsIntPoint;
 struct nsIntRect;
 
 /**
  * A surface is something you can draw on. Instantiate a subclass of this
  * abstract class, and use gfxContext to draw on this surface.
  */
 class THEBES_API gfxASurface {
 public:
+#ifdef MOZILLA_INTERNAL_API
     nsrefcnt AddRef(void);
     nsrefcnt Release(void);
 
+    // These functions exist so that browsercomps can refcount a gfxASurface
+    virtual nsresult AddRefExternal(void)
+    {
+      return AddRef();
+    }
+    virtual nsresult ReleaseExternal(void)
+    {
+      return Release();
+    }
+#else
+    virtual nsresult AddRef(void);
+    virtual nsresult Release(void);
+#endif
+
 public:
     /**
      * The format for an image surface. For all formats with alpha data, 0
      * means transparent, 1 or 255 means fully opaque.
      */
     typedef enum {
         ImageFormatARGB32, ///< ARGB data in native endianness, using premultiplied alpha
         ImageFormatRGB24,  ///< xRGB data in native endianness
--- a/gfx/thebes/gfxCoreTextShaper.cpp
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -103,79 +103,69 @@ gfxCoreTextShaper::~gfxCoreTextShaper()
         ::CFRelease(mAttributesDict);
     }
     if (mCTFont) {
         ::CFRelease(mCTFont);
     }
 }
 
 bool
-gfxCoreTextShaper::InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript)
+gfxCoreTextShaper::ShapeWord(gfxContext      *aContext,
+                             gfxShapedWord   *aShapedWord,
+                             const PRUnichar *aText)
 {
-    // aRunStart and aRunLength define the section of the textRun and of aString
-    // that is to be drawn with this particular font
-
-    bool disableLigatures = (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
-
     // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out.
 
-    bool isRTL = aTextRun->IsRightToLeft();
+    bool isRightToLeft = aShapedWord->IsRightToLeft();
+    PRUint32 length = aShapedWord->Length();
 
     // we need to bidi-wrap the text if the run is RTL,
     // or if it is an LTR run but may contain (overridden) RTL chars
-    bool bidiWrap = isRTL;
-    if (!bidiWrap && (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0) {
+    bool bidiWrap = isRightToLeft;
+    if (!bidiWrap && !aShapedWord->TextIs8Bit()) {
+        const PRUnichar *text = aShapedWord->TextUnicode();
         PRUint32 i;
-        for (i = aRunStart; i < aRunStart + aRunLength; ++i) {
-            if (gfxFontUtils::PotentialRTLChar(aString[i])) {
+        for (i = 0; i < length; ++i) {
+            if (gfxFontUtils::PotentialRTLChar(text[i])) {
                 bidiWrap = true;
                 break;
             }
         }
     }
 
     // If there's a possibility of any bidi, we wrap the text with direction overrides
     // to ensure neutrals or characters that were bidi-overridden in HTML behave properly.
     const UniChar beginLTR[]    = { 0x202d, 0x20 };
     const UniChar beginRTL[]    = { 0x202e, 0x20 };
     const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c };
 
     PRUint32 startOffset;
     CFStringRef stringObj;
     if (bidiWrap) {
-        startOffset = isRTL ?
-            sizeof(beginRTL) / sizeof(beginRTL[0]) : sizeof(beginLTR) / sizeof(beginLTR[0]);
+        startOffset = isRightToLeft ?
+            mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR);
         CFMutableStringRef mutableString =
             ::CFStringCreateMutable(kCFAllocatorDefault,
-                                    aRunLength + startOffset +
-                                    sizeof(endBidiWrap) / sizeof(endBidiWrap[0]));
+                                    length + startOffset + mozilla::ArrayLength(endBidiWrap));
         ::CFStringAppendCharacters(mutableString,
-                                   isRTL ? beginRTL : beginLTR,
+                                   isRightToLeft ? beginRTL : beginLTR,
                                    startOffset);
-        ::CFStringAppendCharacters(mutableString,
-                                   aString + aRunStart, aRunLength);
+        ::CFStringAppendCharacters(mutableString, aText, length);
         ::CFStringAppendCharacters(mutableString,
-                                   endBidiWrap,
-                                   sizeof(endBidiWrap) / sizeof(endBidiWrap[0]));
+                                   endBidiWrap, mozilla::ArrayLength(endBidiWrap));
         stringObj = mutableString;
     } else {
         startOffset = 0;
         stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
-                                                         aString + aRunStart,
-                                                         aRunLength,
+                                                         aText, length,
                                                          kCFAllocatorNull);
     }
 
     CFDictionaryRef attrObj;
-    if (disableLigatures) {
+    if (aShapedWord->DisableLigatures()) {
         // For letterspacing (or maybe other situations) we need to make a copy of the CTFont
         // with the ligature feature disabled
         CTFontRef ctFont =
             CreateCTFontWithDisabledLigatures(::CTFontGetSize(mCTFont));
 
         attrObj =
             ::CFDictionaryCreate(kCFAllocatorDefault,
                                  (const void**) &kCTFontAttributeName,
@@ -204,69 +194,64 @@ gfxCoreTextShaper::InitTextRun(gfxContex
     CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line);
     PRUint32 numRuns = ::CFArrayGetCount(glyphRuns);
 
     // Iterate through the glyph runs.
     // Note that this includes the bidi wrapper, so we have to be careful
     // not to include the extra glyphs from there
     bool success = true;
     for (PRUint32 runIndex = 0; runIndex < numRuns; runIndex++) {
-        CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
-        if (SetGlyphsFromRun(aTextRun, aCTRun, startOffset,
-                             aRunStart, aRunLength) != NS_OK) {
+        CTRunRef aCTRun =
+            (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
+        if (SetGlyphsFromRun(aShapedWord, aCTRun, startOffset) != NS_OK) {
             success = false;
             break;
         }
     }
 
     ::CFRelease(line);
 
     return success;
 }
 
 #define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data;
                             // some testing indicates that 90%+ of glyph runs will fit
                             // without requiring a separate allocation
 
 nsresult
-gfxCoreTextShaper::SetGlyphsFromRun(gfxTextRun *aTextRun,
+gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedWord *aShapedWord,
                                     CTRunRef aCTRun,
-                                    PRInt32 aStringOffset, // offset in the string used to build the CTLine
-                                    PRInt32 aRunStart,     // starting offset of this font run in the gfxTextRun
-                                    PRInt32 aRunLength)    // length of this font run in characters
+                                    PRInt32 aStringOffset)
 {
-    // The textRun has been bidi-wrapped; aStringOffset is the number
+    // The word has been bidi-wrapped; aStringOffset is the number
     // of chars at the beginning of the CTLine that we should skip.
-    // aRunStart and aRunLength define the range of characters
-    // within the textRun that are "real" data we need to handle.
     // aCTRun is a glyph run from the CoreText layout process.
 
-    bool isLTR = !aTextRun->IsRightToLeft();
-    PRInt32 direction = isLTR ? 1 : -1;
+    PRInt32 direction = aShapedWord->IsRightToLeft() ? -1 : 1;
 
     PRInt32 numGlyphs = ::CTRunGetGlyphCount(aCTRun);
     if (numGlyphs == 0) {
         return NS_OK;
     }
 
+    PRInt32 wordLength = aShapedWord->Length();
+
     // character offsets get really confusing here, as we have to keep track of
     // (a) the text in the actual textRun we're constructing
-    // (b) the "font run" being rendered with the current font, defined by aRunStart and aRunLength
-    //     parameters to InitTextRun
     // (c) the string that was handed to CoreText, which contains the text of the font run
     //     plus directional-override padding
     // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line
     //     (but may extend beyond the actual font run into the bidi wrapping text).
     //     aStringOffset tells us how many initial characters of the line to ignore.
 
     // get the source string range within the CTLine's text
     CFRange stringRange = ::CTRunGetStringRange(aCTRun);
     // skip the run if it is entirely outside the actual range of the font run
     if (stringRange.location - aStringOffset + stringRange.length <= 0 ||
-        stringRange.location - aStringOffset >= aRunLength) {
+        stringRange.location - aStringOffset >= wordLength) {
         return NS_OK;
     }
 
     // retrieve the laid-out glyph data from the CTRun
     nsAutoArrayPtr<CGGlyph> glyphsArray;
     nsAutoArrayPtr<CGPoint> positionsArray;
     nsAutoArrayPtr<CFIndex> glyphToCharArray;
     const CGGlyph* glyphs = NULL;
@@ -312,17 +297,16 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
         ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get());
         glyphToChar = glyphToCharArray.get();
     }
 
     double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), NULL, NULL, NULL);
 
     nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
     gfxTextRun::CompressedGlyph g;
-    const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
 
     // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph
     // to a source text character; we also need the charindex-to-glyphindex mapping to
     // find the glyph for a given char. Note that some chars may not map to any glyph
     // (ligature continuations), and some may map to several glyphs (eg Indic split vowels).
     // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we
     // record the last glyph index for cases where the char maps to several glyphs,
     // so that our clumping will include all the glyph fragments for the character.
@@ -352,31 +336,33 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
     // and extend it until it includes the character associated with the first glyph;
     // we also extend it as long as there are "holes" in the range of glyphs. So we
     // will eventually have a contiguous sequence of characters, starting at the beginning
     // of the range, that map to a contiguous sequence of glyphs, starting at the beginning
     // of the glyph array. That's a clump; then we update the starting positions and repeat.
     //
     // NB: In the case of RTL layouts, we iterate over the stringRange in reverse.
     //
-    // This may find characters that fall outside the range aRunStart:aRunLength,
+
+    // This may find characters that fall outside the range 0:wordLength,
     // so we won't necessarily use everything we find here.
 
+    bool isRightToLeft = aShapedWord->IsRightToLeft();
     PRInt32 glyphStart = 0; // looking for a clump that starts at this glyph index
-    PRInt32 charStart = isLTR ?
-        0 : stringRange.length-1; // and this char index (in the stringRange of the glyph run)
+    PRInt32 charStart = isRightToLeft ?
+        stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run)
 
     while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for
 
         bool inOrder = true;
         PRInt32 charEnd = glyphToChar[glyphStart] - stringRange.location;
         NS_ASSERTION(charEnd >= 0 && charEnd < stringRange.length,
                      "glyph-to-char mapping points outside string range");
         PRInt32 glyphEnd = glyphStart;
-        PRInt32 charLimit = isLTR ? stringRange.length : -1;
+        PRInt32 charLimit = isRightToLeft ? -1 : stringRange.length;
         do {
             // This is normally executed once for each iteration of the outer loop,
             // but in unusual cases where the character/glyph association is complex,
             // the initial character range might correspond to a non-contiguous
             // glyph range with "holes" in it. If so, we will repeat this loop to
             // extend the character range until we have a contiguous glyph sequence.
             NS_ASSERTION((direction > 0 && charEnd < charLimit) ||
                          (direction < 0 && charEnd > charLimit),
@@ -405,93 +391,95 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
 
             // check whether all glyphs in the range are associated with the characters
             // in our clump; if not, we have a discontinuous range, and should extend it
             // unless we've reached the end of the text
             bool allGlyphsAreWithinCluster = true;
             PRInt32 prevGlyphCharIndex = charStart;
             for (PRInt32 i = glyphStart; i < glyphEnd; ++i) {
                 PRInt32 glyphCharIndex = glyphToChar[i] - stringRange.location;
-                if (isLTR) {
+                if (isRightToLeft) {
+                    if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
+                        allGlyphsAreWithinCluster = false;
+                        break;
+                    }
+                    if (glyphCharIndex > prevGlyphCharIndex) {
+                        inOrder = false;
+                    }
+                    prevGlyphCharIndex = glyphCharIndex;
+                } else {
                     if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
                         allGlyphsAreWithinCluster = false;
                         break;
                     }
                     if (glyphCharIndex < prevGlyphCharIndex) {
                         inOrder = false;
                     }
                     prevGlyphCharIndex = glyphCharIndex;
-                } else {
-                    if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
-                        allGlyphsAreWithinCluster = false;
-                        break;
-                    }
-                    if (glyphCharIndex > prevGlyphCharIndex) {
-                        inOrder = false;
-                    }
-                    prevGlyphCharIndex = glyphCharIndex;
                 }
             }
             if (allGlyphsAreWithinCluster) {
                 break;
             }
         } while (charEnd != charLimit);
 
         NS_ASSERTION(glyphStart < glyphEnd, "character/glyph clump contains no glyphs!");
         NS_ASSERTION(charStart != charEnd, "character/glyph contains no characters!");
 
         // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd;
         // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature),
         // and endCharIndex to the limit (position beyond the last char),
         // adjusting for the offset of the stringRange relative to the textRun.
         PRInt32 baseCharIndex, endCharIndex;
-        if (isLTR) {
+        if (isRightToLeft) {
+            while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
+                charEnd--;
+            }
+            baseCharIndex = charEnd + stringRange.location - aStringOffset + 1;
+            endCharIndex = charStart + stringRange.location - aStringOffset + 1;
+        } else {
             while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
                 charEnd++;
             }
-            baseCharIndex = charStart + stringRange.location - aStringOffset + aRunStart;
-            endCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart;
-        } else {
-            while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
-                charEnd--;
-            }
-            baseCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart + 1;
-            endCharIndex = charStart + stringRange.location - aStringOffset + aRunStart + 1;
+            baseCharIndex = charStart + stringRange.location - aStringOffset;
+            endCharIndex = charEnd + stringRange.location - aStringOffset;
         }
 
         // Then we check if the clump falls outside our actual string range; if so, just go to the next.
-        if (endCharIndex <= aRunStart || baseCharIndex >= aRunStart + aRunLength) {
+        if (endCharIndex <= 0 || baseCharIndex >= wordLength) {
             glyphStart = glyphEnd;
             charStart = charEnd;
             continue;
         }
-        // Ensure we won't try to go beyond the valid length of the textRun's text
-        baseCharIndex = NS_MAX(baseCharIndex, aRunStart);
-        endCharIndex = NS_MIN(endCharIndex, aRunStart + aRunLength);
+        // Ensure we won't try to go beyond the valid length of the word's text
+        baseCharIndex = NS_MAX(baseCharIndex, 0);
+        endCharIndex = NS_MIN(endCharIndex, wordLength);
 
         // Now we're ready to set the glyph info in the textRun; measure the glyph width
         // of the first (perhaps only) glyph, to see if it is "Simple"
+        PRInt32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
         double toNextGlyph;
         if (glyphStart < numGlyphs-1) {
             toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
         } else {
             toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
         }
         PRInt32 advance = PRInt32(toNextGlyph * appUnitsPerDevUnit);
 
         // Check if it's a simple one-to-one mapping
         PRInt32 glyphsInClump = glyphEnd - glyphStart;
         if (glyphsInClump == 1 &&
             gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) &&
             gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-            aTextRun->IsClusterStart(baseCharIndex) &&
+            aShapedWord->IsClusterStart(baseCharIndex) &&
             positions[glyphStart].y == 0.0)
         {
-            aTextRun->SetSimpleGlyph(baseCharIndex,
-                                     g.SetSimpleGlyph(advance, glyphs[glyphStart]));
+            aShapedWord->SetSimpleGlyph(baseCharIndex,
+                                        g.SetSimpleGlyph(advance,
+                                                         glyphs[glyphStart]));
         } else {
             // collect all glyphs in a list to be assigned to the first char;
             // there must be at least one in the clump, and we already measured its advance,
             // hence the placement of the loop-exit test and the measurement of the next glyph
             while (1) {
                 gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
                 details->mGlyphID = glyphs[glyphStart];
                 details->mXOffset = 0;
@@ -504,28 +492,28 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT
                     toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
                 } else {
                     toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
                 }
                 advance = PRInt32(toNextGlyph * appUnitsPerDevUnit);
             }
 
             gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(baseCharIndex),
+            g.SetComplex(aShapedWord->IsClusterStart(baseCharIndex),
                          true, detailedGlyphs.Length());
-            aTextRun->SetGlyphs(baseCharIndex, g, detailedGlyphs.Elements());
+            aShapedWord->SetGlyphs(baseCharIndex, g, detailedGlyphs.Elements());
 
             detailedGlyphs.Clear();
         }
 
         // the rest of the chars in the group are ligature continuations, no associated glyphs
-        while (++baseCharIndex != endCharIndex && baseCharIndex < aRunStart + aRunLength) {
-            g.SetComplex(inOrder && aTextRun->IsClusterStart(baseCharIndex),
+        while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
+            g.SetComplex(inOrder && aShapedWord->IsClusterStart(baseCharIndex),
                          false, 0);
-            aTextRun->SetGlyphs(baseCharIndex, g, nsnull);
+            aShapedWord->SetGlyphs(baseCharIndex, g, nsnull);
         }
 
         glyphStart = glyphEnd;
         charStart = charEnd;
     }
 
     return NS_OK;
 }
--- a/gfx/thebes/gfxCoreTextShaper.h
+++ b/gfx/thebes/gfxCoreTextShaper.h
@@ -52,35 +52,30 @@
 class gfxMacFont;
 
 class gfxCoreTextShaper : public gfxFontShaper {
 public:
     gfxCoreTextShaper(gfxMacFont *aFont);
 
     virtual ~gfxCoreTextShaper();
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText);
 
     // clean up static objects that may have been cached
     static void Shutdown();
 
 protected:
     CTFontRef mCTFont;
     CFDictionaryRef mAttributesDict;
 
-    nsresult SetGlyphsFromRun(gfxTextRun *aTextRun,
+    nsresult SetGlyphsFromRun(gfxShapedWord *aShapedWord,
                               CTRunRef aCTRun,
-                              PRInt32 aStringOffset,
-                              PRInt32 aLayoutStart,
-                              PRInt32 aLayoutLength);
+                              PRInt32 aStringOffset);
 
     CTFontRef CreateCTFontWithDisabledLigatures(CGFloat aSize);
 
     static void CreateDefaultFeaturesDescriptor();
 
     static CTFontDescriptorRef GetDefaultFeaturesDescriptor() {
         if (sDefaultFeaturesDescriptor == NULL) {
             CreateDefaultFeaturesDescriptor();
--- a/gfx/thebes/gfxDWriteShaper.cpp
+++ b/gfx/thebes/gfxDWriteShaper.cpp
@@ -39,261 +39,219 @@
 #include "gfxWindowsPlatform.h"
 
 #include <dwrite.h>
 
 #include "gfxDWriteTextAnalysis.h"
 
 #include "nsCRT.h"
 
-#define MAX_RANGE_LENGTH 25000
-
 bool
-gfxDWriteShaper::InitTextRun(gfxContext *aContext,
-                             gfxTextRun *aTextRun,
-                             const PRUnichar *aString,
-                             PRUint32 aRunStart,
-                             PRUint32 aRunLength,
-                             PRInt32 aRunScript)
+gfxDWriteShaper::ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString)
 {
     HRESULT hr;
     // TODO: Handle TEST_DISABLE_OPTIONAL_LIGATURES
 
     DWRITE_READING_DIRECTION readingDirection = 
-        aTextRun->IsRightToLeft()
+        aShapedWord->IsRightToLeft()
             ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT
             : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
 
     gfxDWriteFont *font = static_cast<gfxDWriteFont*>(mFont);
 
-    gfxTextRun::CompressedGlyph g;
-
-    nsRefPtr<IDWriteTextAnalyzer> analyzer;
+    gfxShapedWord::CompressedGlyph g;
 
-    hr = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
-        CreateTextAnalyzer(getter_AddRefs(analyzer));
-    if (FAILED(hr)) {
+    IDWriteTextAnalyzer *analyzer =
+        gfxWindowsPlatform::GetPlatform()->GetDWriteAnalyzer();
+    if (!analyzer) {
         return false;
     }
 
     /**
-     * There's an internal 16-bit limit on some things inside the analyzer.
-     * to be on the safe side here we split up any ranges over MAX_RANGE_LENGTH 
-     * characters.
-     * TODO: Figure out what exactly is going on, and what is a safe number, and 
-     * why.
+     * There's an internal 16-bit limit on some things inside the analyzer,
+     * but we never attempt to shape a word longer than 64K characters
+     * in a single gfxShapedWord, so we cannot exceed that limit.
      */
-    bool result = true;
-    UINT32 rangeOffset = 0;
-    while (rangeOffset < aRunLength) {
-        PRUint32 rangeLen = NS_MIN<PRUint32>(aRunLength - rangeOffset,
-                                             MAX_RANGE_LENGTH);
-        if (rangeOffset + rangeLen < aRunLength) {
-            // Iterate backwards to find a decent place to break shaping:
-            // look for a whitespace char, and if that's not found then
-            // at least a char where IsClusterStart is true.
-            // However, we never iterate back further than half-way;
-            // if a decent break is not found by then, we just chop anyway
-            // at the original range length.
-            PRUint32 adjRangeLen = 0;
-            const PRUnichar *rangeStr = aString + aRunStart + rangeOffset;
-            for (PRUint32 i = rangeLen; i > MAX_RANGE_LENGTH / 2; i--) {
-                if (nsCRT::IsAsciiSpace(rangeStr[i])) {
-                    adjRangeLen = i;
-                    break;
-                }
-                if (adjRangeLen == 0 &&
-                    aTextRun->IsClusterStart(aRunStart + rangeOffset + i)) {
-                    adjRangeLen = i;
-                }
-            }
-            if (adjRangeLen != 0) {
-                rangeLen = adjRangeLen;
-            }
-        }
+    UINT32 length = aShapedWord->Length();
+
+    TextAnalysis analysis(aString, length, NULL, readingDirection);
+    TextAnalysis::Run *runHead;
+    hr = analysis.GenerateResults(analyzer, &runHead);
+
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to generate results.");
+        return false;
+    }
+
+    PRUint32 appUnitsPerDevPixel = aShapedWord->AppUnitsPerDevUnit();
 
-        PRUint32 rangeStart = aRunStart + rangeOffset;
-        rangeOffset += rangeLen;
-        TextAnalysis analysis(aString + rangeStart, rangeLen,
-            NULL, 
-            readingDirection);
-        TextAnalysis::Run *runHead;
-        DWRITE_LINE_BREAKPOINT *linebreaks;
-        hr = analysis.GenerateResults(analyzer, &runHead, &linebreaks);
-
-        if (FAILED(hr)) {
-            NS_WARNING("Analyzer failed to generate results.");
-            result = false;
-            break;
-        }
-
-        PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit();
-
-        UINT32 maxGlyphs = 0;
+    UINT32 maxGlyphs = 0;
 trymoreglyphs:
-        if ((PR_UINT32_MAX - 3 * rangeLen / 2 + 16) < maxGlyphs) {
-            // This isn't going to work, we're going to cross the UINT32 upper
-            // limit. Next range it is.
-            continue;
-        }
-        maxGlyphs += 3 * rangeLen / 2 + 16;
+    if ((PR_UINT32_MAX - 3 * length / 2 + 16) < maxGlyphs) {
+        // This isn't going to work, we're going to cross the UINT32 upper
+        // limit. Give up.
+        NS_WARNING("Shaper needs to generate more than 2^32 glyphs?!");
+        return false;
+    }
+    maxGlyphs += 3 * length / 2 + 16;
 
-        nsAutoTArray<UINT16, 400> clusters;
-        nsAutoTArray<UINT16, 400> indices;
-        nsAutoTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties;
-        nsAutoTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties;
-        if (!clusters.SetLength(rangeLen) ||
-            !indices.SetLength(maxGlyphs) || 
-            !textProperties.SetLength(maxGlyphs) ||
-            !glyphProperties.SetLength(maxGlyphs)) {
-                continue;
-        }
+    nsAutoTArray<UINT16, 400> clusters;
+    nsAutoTArray<UINT16, 400> indices;
+    nsAutoTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties;
+    nsAutoTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties;
+    if (!clusters.SetLength(length) ||
+        !indices.SetLength(maxGlyphs) || 
+        !textProperties.SetLength(maxGlyphs) ||
+        !glyphProperties.SetLength(maxGlyphs)) {
+        NS_WARNING("Shaper failed to allocate memory.");
+        return false;
+    }
 
-        UINT32 actualGlyphs;
+    UINT32 actualGlyphs;
 
-        hr = analyzer->GetGlyphs(aString + rangeStart, rangeLen,
+    hr = analyzer->GetGlyphs(aString, length,
             font->GetFontFace(), FALSE, 
             readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
             &runHead->mScript, NULL, NULL, NULL, NULL, 0,
             maxGlyphs, clusters.Elements(), textProperties.Elements(),
             indices.Elements(), glyphProperties.Elements(), &actualGlyphs);
 
-        if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
-            // We increase the amount of glyphs and try again.
-            goto trymoreglyphs;
-        }
-        if (FAILED(hr)) {
-            NS_WARNING("Analyzer failed to get glyphs.");
-            result = false;
-            break;
-        }
+    if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
+        // We increase the amount of glyphs and try again.
+        goto trymoreglyphs;
+    }
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to get glyphs.");
+        return false;
+    }
+
+    WORD gID = indices[0];
+    nsAutoTArray<FLOAT, 400> advances;
+    nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets;
+    if (!advances.SetLength(actualGlyphs) || 
+        !glyphOffsets.SetLength(actualGlyphs)) {
+        NS_WARNING("Shaper failed to allocate memory.");
+        return false;
+    }
 
-        WORD gID = indices[0];
-        nsAutoTArray<FLOAT, 400> advances;
-        nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets;
-        if (!advances.SetLength(actualGlyphs) || 
-            !glyphOffsets.SetLength(actualGlyphs)) {
+    if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) {
+        hr = analyzer->GetGdiCompatibleGlyphPlacements(
+                                          aString,
+                                          clusters.Elements(),
+                                          textProperties.Elements(),
+                                          length,
+                                          indices.Elements(),
+                                          glyphProperties.Elements(),
+                                          actualGlyphs,
+                                          font->GetFontFace(),
+                                          font->GetAdjustedSize(),
+                                          1.0,
+                                          nsnull,
+                                          FALSE,
+                                          FALSE,
+                                          FALSE,
+                                          &runHead->mScript,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          0,
+                                          advances.Elements(),
+                                          glyphOffsets.Elements());
+    } else {
+        hr = analyzer->GetGlyphPlacements(aString,
+                                          clusters.Elements(),
+                                          textProperties.Elements(),
+                                          length,
+                                          indices.Elements(),
+                                          glyphProperties.Elements(),
+                                          actualGlyphs,
+                                          font->GetFontFace(),
+                                          font->GetAdjustedSize(),
+                                          FALSE,
+                                          FALSE,
+                                          &runHead->mScript,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          0,
+                                          advances.Elements(),
+                                          glyphOffsets.Elements());
+    }
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to get glyph placements.");
+        return false;
+    }
+
+    nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
+
+    for (unsigned int c = 0; c < length; c++) {
+        PRUint32 k = clusters[c];
+        PRUint32 absC = c;
+
+        if (c > 0 && k == clusters[c - 1]) {
+            g.SetComplex(aShapedWord->IsClusterStart(absC), false, 0);
+            aShapedWord->SetGlyphs(absC, g, nsnull);
+            // This is a cluster continuation. No glyph here.
             continue;
         }
 
-        if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) {
-            hr = analyzer->GetGdiCompatibleGlyphPlacements(
-                                              aString + rangeStart,
-                                              clusters.Elements(),
-                                              textProperties.Elements(),
-                                              rangeLen,
-                                              indices.Elements(),
-                                              glyphProperties.Elements(),
-                                              actualGlyphs,
-                                              font->GetFontFace(),
-                                              font->GetAdjustedSize(),
-                                              1.0,
-                                              nsnull,
-                                              FALSE,
-                                              FALSE,
-                                              FALSE,
-                                              &runHead->mScript,
-                                              NULL,
-                                              NULL,
-                                              NULL,
-                                              0,
-                                              advances.Elements(),
-                                              glyphOffsets.Elements());
+        // Count glyphs for this character
+        PRUint32 glyphCount = actualGlyphs - k;
+        PRUint32 nextClusterOffset;
+        for (nextClusterOffset = c + 1; 
+            nextClusterOffset < length; ++nextClusterOffset) {
+            if (clusters[nextClusterOffset] > k) {
+                glyphCount = clusters[nextClusterOffset] - k;
+                break;
+            }
+        }
+        PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel);
+        if (glyphCount == 1 && advance >= 0 &&
+            glyphOffsets[k].advanceOffset == 0 &&
+            glyphOffsets[k].ascenderOffset == 0 &&
+            aShapedWord->IsClusterStart(absC) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(indices[k])) {
+              aShapedWord->SetSimpleGlyph(absC, 
+                                          g.SetSimpleGlyph(advance, 
+                                                           indices[k]));
         } else {
-            hr = analyzer->GetGlyphPlacements(aString + rangeStart,
-                                              clusters.Elements(),
-                                              textProperties.Elements(),
-                                              rangeLen,
-                                              indices.Elements(),
-                                              glyphProperties.Elements(),
-                                              actualGlyphs,
-                                              font->GetFontFace(),
-                                              font->GetAdjustedSize(),
-                                              FALSE,
-                                              FALSE,
-                                              &runHead->mScript,
-                                              NULL,
-                                              NULL,
-                                              NULL,
-                                              0,
-                                              advances.Elements(),
-                                              glyphOffsets.Elements());
-        }
-        if (FAILED(hr)) {
-            NS_WARNING("Analyzer failed to get glyph placements.");
-            result = false;
-            break;
-        }
-
-        nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
-
-        for (unsigned int c = 0; c < rangeLen; c++) {
-            PRUint32 k = clusters[c];
-            PRUint32 absC = rangeStart + c;
-
-            if (c > 0 && k == clusters[c - 1]) {
-                g.SetComplex(aTextRun->IsClusterStart(absC), false, 0);
-                aTextRun->SetGlyphs(absC, g, nsnull);
-                // This is a cluster continuation. No glyph here.
-                continue;
-            }
-
-            // Count glyphs for this character
-            PRUint32 glyphCount = actualGlyphs - k;
-            PRUint32 nextClusterOffset;
-            for (nextClusterOffset = c + 1; 
-                nextClusterOffset < rangeLen; ++nextClusterOffset) {
-                if (clusters[nextClusterOffset] > k) {
-                    glyphCount = clusters[nextClusterOffset] - k;
-                    break;
+            if (detailedGlyphs.Length() < glyphCount) {
+                if (!detailedGlyphs.AppendElements(
+                    glyphCount - detailedGlyphs.Length())) {
+                    continue;
                 }
             }
-            PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel);
-            if (glyphCount == 1 && advance >= 0 &&
-                glyphOffsets[k].advanceOffset == 0 &&
-                glyphOffsets[k].ascenderOffset == 0 &&
-                aTextRun->IsClusterStart(absC) &&
-                gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-                gfxTextRun::CompressedGlyph::IsSimpleGlyphID(indices[k])) {
-                  aTextRun->SetSimpleGlyph(absC, 
-                                          g.SetSimpleGlyph(advance, 
-                                                           indices[k]));
-            } else {
-                if (detailedGlyphs.Length() < glyphCount) {
-                    if (!detailedGlyphs.AppendElements(
-                        glyphCount - detailedGlyphs.Length())) {
-                        continue;
-                    }
+            float totalAdvance = 0;
+            for (unsigned int z = 0; z < glyphCount; z++) {
+                detailedGlyphs[z].mGlyphID = indices[k + z];
+                detailedGlyphs[z].mAdvance = 
+                    (PRInt32)(advances[k + z]
+                       * appUnitsPerDevPixel);
+                if (readingDirection == 
+                    DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) {
+                    detailedGlyphs[z].mXOffset = 
+                        (totalAdvance + 
+                          glyphOffsets[k + z].advanceOffset)
+                        * appUnitsPerDevPixel;
+                } else {
+                    detailedGlyphs[z].mXOffset = 
+                        glyphOffsets[k + z].advanceOffset *
+                        appUnitsPerDevPixel;
                 }
-                float totalAdvance = 0;
-                for (unsigned int z = 0; z < glyphCount; z++) {
-                    detailedGlyphs[z].mGlyphID = indices[k + z];
-                    detailedGlyphs[z].mAdvance = 
-                        (PRInt32)(advances[k + z]
-                           * appUnitsPerDevPixel);
-                    if (readingDirection == 
-                        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) {
-                        detailedGlyphs[z].mXOffset = 
-                            (totalAdvance + 
-                              glyphOffsets[k + z].advanceOffset)
-                            * appUnitsPerDevPixel;
-                    } else {
-                        detailedGlyphs[z].mXOffset = 
-                            glyphOffsets[k + z].advanceOffset *
-                            appUnitsPerDevPixel;
-                    }
-                    detailedGlyphs[z].mYOffset = 
-                        -glyphOffsets[k + z].ascenderOffset *
-                        appUnitsPerDevPixel;
-                    totalAdvance += advances[k + z];
-                }
-                aTextRun->SetGlyphs(
-                    absC,
-                    g.SetComplex(aTextRun->IsClusterStart(absC),
-                                 true,
-                                 glyphCount),
-                    detailedGlyphs.Elements());
+                detailedGlyphs[z].mYOffset = 
+                    -glyphOffsets[k + z].ascenderOffset *
+                    appUnitsPerDevPixel;
+                totalAdvance += advances[k + z];
             }
+            aShapedWord->SetGlyphs(
+                absC,
+                g.SetComplex(aShapedWord->IsClusterStart(absC),
+                             true,
+                             glyphCount),
+                detailedGlyphs.Elements());
         }
     }
 
-    return result;
+    return true;
 }
--- a/gfx/thebes/gfxDWriteShaper.h
+++ b/gfx/thebes/gfxDWriteShaper.h
@@ -52,17 +52,14 @@ public:
         MOZ_COUNT_CTOR(gfxDWriteShaper);
     }
 
     virtual ~gfxDWriteShaper()
     {
         MOZ_COUNT_DTOR(gfxDWriteShaper);
     }
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString);
 };
 
 #endif /* GFX_DWRITESHAPER_H */
--- a/gfx/thebes/gfxDWriteTextAnalysis.cpp
+++ b/gfx/thebes/gfxDWriteTextAnalysis.cpp
@@ -40,80 +40,54 @@
 TextAnalysis::TextAnalysis(const wchar_t* text,
                            UINT32 textLength,
                            const wchar_t* localeName,
                            DWRITE_READING_DIRECTION readingDirection)
   : mText(text)
   , mTextLength(textLength)
   , mLocaleName(localeName)
   , mReadingDirection(readingDirection)
-  , mBreakpoints(NULL)
-  , mRunHead(NULL)
   , mCurrentRun(NULL)
 {
 }
 
 TextAnalysis::~TextAnalysis()
 {
-    delete [] mBreakpoints;
-    for (Run *run = mRunHead; run;) {
+    // delete runs, except mRunHead which is part of the TextAnalysis object
+    for (Run *run = mRunHead.nextRun; run;) {
         Run *origRun = run;
         run = run->nextRun;
         delete origRun;
     }
 }
 
 STDMETHODIMP 
 TextAnalysis::GenerateResults(IDWriteTextAnalyzer* textAnalyzer,
-                              OUT Run **runHead,
-                              OUT DWRITE_LINE_BREAKPOINT **breakpoints)
+                              OUT Run **runHead)
 {
-    // Analyzes the text using each of the analyzers and returns
-    // their results as a series of runs.
+    // Analyzes the text using the script analyzer and returns
+    // the result as a series of runs.
 
     HRESULT hr = S_OK;
 
     // Initially start out with one result that covers the entire range.
     // This result will be subdivided by the analysis processes.
-    mRunHead = new Run;
-    
-    mRunHead->mTextStart = 0;
-    mRunHead->mTextLength = mTextLength;
-    mRunHead->mBidiLevel = 
+    mRunHead.mTextStart = 0;
+    mRunHead.mTextLength = mTextLength;
+    mRunHead.mBidiLevel = 
         (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
-    mRunHead->nextRun = NULL;
-    mCurrentRun = mRunHead;
-#ifdef USE_DWRITE_BREAKPOINTS
-    delete [] mBreakpoints;
-    mBreakpoints = new DWRITE_LINE_BREAKPOINT[mTextLength];
-#endif
+    mRunHead.nextRun = NULL;
+    mCurrentRun = &mRunHead;
 
     // Call each of the analyzers in sequence, recording their results.
-    if (
-#ifdef USE_DWRITE_BREAKPOINTS
-        SUCCEEDED(hr = textAnalyzer->AnalyzeLineBreakpoints(this,
-                                                            0,
-                                                            mTextLength,
-                                                            this)) && 
-#endif
-        SUCCEEDED(hr = textAnalyzer->AnalyzeBidi(this,
-                                                 0,
-                                                 mTextLength,
-                                                 this)) &&
-        SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this,
+    if (SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this,
                                                    0,
                                                    mTextLength,
-                                                   this)) &&
-        SUCCEEDED(hr = textAnalyzer->AnalyzeNumberSubstitution(this,
-                                                               0,
-                                                               mTextLength,
-                                                               this))) {
-        *breakpoints = mBreakpoints;
-
-        *runHead = mRunHead;
+                                                   this))) {
+        *runHead = &mRunHead;
     }
 
     return hr;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // IDWriteTextAnalysisSource source implementation
@@ -190,21 +164,17 @@ TextAnalysis::GetNumberSubstitution(UINT
 ////////////////////////////////////////////////////////////////////////////////
 // IDWriteTextAnalysisSink implementation
 
 IFACEMETHODIMP 
 TextAnalysis::SetLineBreakpoints(UINT32 textPosition,
                                  UINT32 textLength,
                                  DWRITE_LINE_BREAKPOINT const* lineBreakpoints)
 {
-    if (textLength > 0) {
-        memcpy(mBreakpoints + textPosition,
-               lineBreakpoints,
-               textLength * sizeof(DWRITE_LINE_BREAKPOINT));
-    }
+    // We don't use this for now.
     return S_OK;
 }
 
 
 IFACEMETHODIMP 
 TextAnalysis::SetScriptAnalysis(UINT32 textPosition,
                                 UINT32 textLength,
                                 DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis)
@@ -221,23 +191,17 @@ TextAnalysis::SetScriptAnalysis(UINT32 t
 
 
 IFACEMETHODIMP 
 TextAnalysis::SetBidiLevel(UINT32 textPosition,
                            UINT32 textLength,
                            UINT8 explicitLevel,
                            UINT8 resolvedLevel)
 {
-    SetCurrentRun(textPosition);
-    SplitCurrentRun(textPosition);
-    while (textLength > 0) {
-        Run *run = FetchNextRun(&textLength);
-        run->mBidiLevel = resolvedLevel;
-    }
-
+    // We don't use this for now.
     return S_OK;
 }
 
 
 IFACEMETHODIMP 
 TextAnalysis::SetNumberSubstitution(UINT32 textPosition,
                                     UINT32 textLength,
                                     IDWriteNumberSubstitution* numberSubstitution)
@@ -279,17 +243,17 @@ void TextAnalysis::SetCurrentRun(UINT32 
     // Since the analyzers generally return results in a forward manner,
     // this will usually just return early. If not, find the
     // corresponding run for the text position.
 
     if (mCurrentRun && mCurrentRun->ContainsTextPosition(textPosition)) {
         return;
     }
 
-    for (Run *run = mRunHead; run; run = run->nextRun) {
+    for (Run *run = &mRunHead; run; run = run->nextRun) {
         if (run->ContainsTextPosition(textPosition)) {
             mCurrentRun = run;
             return;
         }
     }
     NS_NOTREACHED("We should always be able to find the text position in one \
         of our runs");
 }
--- a/gfx/thebes/gfxDWriteTextAnalysis.h
+++ b/gfx/thebes/gfxDWriteTextAnalysis.h
@@ -101,18 +101,17 @@ public:
     TextAnalysis(const wchar_t* text,
                  UINT32 textLength,
                  const wchar_t* localeName,
                  DWRITE_READING_DIRECTION readingDirection);
 
     ~TextAnalysis();
 
     STDMETHODIMP GenerateResults(IDWriteTextAnalyzer* textAnalyzer,
-                                 Run **runHead,
-                                 DWRITE_LINE_BREAKPOINT **breakpoints);
+                                 Run **runHead);
 
     // IDWriteTextAnalysisSource implementation
 
     IFACEMETHODIMP GetTextAtPosition(UINT32 textPosition,
                                      OUT WCHAR const** textString,
                                      OUT UINT32* textLength);
 
     IFACEMETHODIMP GetTextBeforePosition(UINT32 textPosition,
@@ -167,16 +166,13 @@ protected:
     UINT32 mTextLength;
     const wchar_t* mText;
     const wchar_t* mLocaleName;
     DWRITE_READING_DIRECTION mReadingDirection;
 
     // Current processing state.
     Run *mCurrentRun;
 
-    // Output
-    Run *mRunHead;
-
-    // We do not use this for now, store anyway
-    DWRITE_LINE_BREAKPOINT *mBreakpoints;
+    // Output is a list of runs starting here
+    Run  mRunHead;
 };
 
 #endif /* GFX_DWRITETEXTANALYSIS_H */
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -420,79 +420,78 @@ gfxFT2FontGroup::WhichSystemFontSupports
 
 #endif // !ANDROID
 
 /**
  * gfxFT2Font
  */
 
 bool
-gfxFT2Font::InitTextRun(gfxContext *aContext,
-                        gfxTextRun *aTextRun,
-                        const PRUnichar *aString,
-                        PRUint32 aRunStart,
-                        PRUint32 aRunLength,
-                        PRInt32 aRunScript,
-                        bool aPreferPlatformShaping)
+gfxFT2Font::ShapeWord(gfxContext *aContext,
+                      gfxShapedWord *aShapedWord,
+                      const PRUnichar *aString,
+                      bool aPreferPlatformShaping)
 {
     bool ok = false;
 
 #ifdef MOZ_GRAPHITE
     if (FontCanSupportGraphite()) {
         if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
             if (!mGraphiteShaper) {
                 mGraphiteShaper = new gfxGraphiteShaper(this);
             }
-            ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength,
-                                              aRunScript);
+            ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString);
         }
     }
 #endif
 
-    if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) {
+    if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) {
         if (!mHarfBuzzShaper) {
             gfxFT2LockedFace face(this);
             mFUnitsConvFactor = face.XScale();
 
             mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
         }
-        ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
-                                          aRunStart, aRunLength, aRunScript);
+        ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString);
     }
 
     if (!ok) {
-        AddRange(aTextRun, aString, aRunStart, aRunLength);
+        AddRange(aShapedWord, aString);
     }
 
-    aTextRun->AdjustAdvancesForSyntheticBold(aContext, aRunStart, aRunLength);
+    if (IsSyntheticBold()) {
+        float synBoldOffset =
+            GetSyntheticBoldOffset() * CalcXScale(aContext);
+        aShapedWord->AdjustAdvancesForSyntheticBold(synBoldOffset);
+    }
 
     return true;
 }
 
 void
-gfxFT2Font::AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len)
+gfxFT2Font::AddRange(gfxShapedWord *aShapedWord, const PRUnichar *str)
 {
-    const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+    const PRUint32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit();
     // we'll pass this in/figure it out dynamically, but at this point there can be only one face.
     gfxFT2LockedFace faceLock(this);
     FT_Face face = faceLock.get();
 
-    gfxTextRun::CompressedGlyph g;
+    gfxShapedWord::CompressedGlyph g;
 
     const gfxFT2Font::CachedGlyphData *cgd = nsnull, *cgdNext = nsnull;
 
     FT_UInt spaceGlyph = GetSpaceGlyph();
 
+    PRUint32 len = aShapedWord->Length();
     for (PRUint32 i = 0; i < len; i++) {
-        PRUint32 ch = str[offset + i];
+        PRUnichar ch = str[i];
 
         if (ch == 0) {
             // treat this null byte as a missing glyph, don't create a glyph for it
-            aTextRun->SetMissingGlyph(offset + i, 0);
+            aShapedWord->SetMissingGlyph(i, 0, this);
             continue;
         }
 
         NS_ASSERTION(!gfxFontGroup::IsInvalidChar(ch), "Invalid char detected");
 
         if (cgdNext) {
             cgd = cgdNext;
             cgdNext = nsnull;
@@ -503,22 +502,22 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu
         FT_UInt gid = cgd->glyphIndex;
         PRInt32 advance = 0;
 
         if (gid == 0) {
             advance = -1; // trigger the missing glyphs case below
         } else {
             // find next character and its glyph -- in case they exist
             // and exist in the current font face -- to compute kerning
-            PRUint32 chNext = 0;
+            PRUnichar chNext = 0;
             FT_UInt gidNext = 0;
             FT_Pos lsbDeltaNext = 0;
 
             if (FT_HAS_KERNING(face) && i + 1 < len) {
-                chNext = str[offset + i + 1];
+                chNext = str[i + 1];
                 if (chNext != 0) {
                     cgdNext = GetGlyphDataForChar(chNext);
                     gidNext = cgdNext->glyphIndex;
                     if (gidNext && gidNext != spaceGlyph)
                         lsbDeltaNext = cgdNext->lsbDelta;
                 }
             }
 
@@ -536,37 +535,33 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu
                 }
             }
 
             // convert 26.6 fixed point to app units
             // round rather than truncate to nearest pixel
             // because these advances are often scaled
             advance = ((advance * appUnitsPerDevUnit + 32) >> 6);
         }
-#ifdef DEBUG_thebes_2
-        printf(" gid=%d, advance=%d (%s)\n", gid, advance,
-               NS_LossyConvertUTF16toASCII(font->GetName()).get());
-#endif
 
         if (advance >= 0 &&
-            gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
-            gfxTextRun::CompressedGlyph::IsSimpleGlyphID(gid)) {
-            aTextRun->SetSimpleGlyph(offset + i, g.SetSimpleGlyph(advance, gid));
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gid)) {
+            aShapedWord->SetSimpleGlyph(i, g.SetSimpleGlyph(advance, gid));
         } else if (gid == 0) {
             // gid = 0 only happens when the glyph is missing from the font
-            aTextRun->SetMissingGlyph(offset + i, ch);
+            aShapedWord->SetMissingGlyph(i, ch, this);
         } else {
             gfxTextRun::DetailedGlyph details;
             details.mGlyphID = gid;
             NS_ASSERTION(details.mGlyphID == gid, "Seriously weird glyph ID detected!");
             details.mAdvance = advance;
             details.mXOffset = 0;
             details.mYOffset = 0;
-            g.SetComplex(aTextRun->IsClusterStart(offset + i), true, 1);
-            aTextRun->SetGlyphs(offset + i, g, &details);
+            g.SetComplex(aShapedWord->IsClusterStart(i), true, 1);
+            aShapedWord->SetGlyphs(i, g, &details);
         }
     }
 }
 
 gfxFT2Font::gfxFT2Font(cairo_scaled_font_t *aCairoFont,
                        FT2FontEntry *aFontEntry,
                        const gfxFontStyle *aFontStyle,
                        bool aNeedsBold)
--- a/gfx/thebes/gfxFT2Fonts.h
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -92,27 +92,24 @@ public: // new functions
             // this is a new entry, fill it
             FillGlyphDataForChar(ch, &entry->mData);
         }
 
         return &entry->mData;
     }
 
 protected:
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString,
+                           bool aPreferPlatformShaping = false);
 
     void FillGlyphDataForChar(PRUint32 ch, CachedGlyphData *gd);
 
-    void AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len);
+    void AddRange(gfxShapedWord *aShapedWord, const PRUnichar *str);
 
     typedef nsBaseHashtableET<nsUint32HashKey, CachedGlyphData> CharGlyphMapEntryType;
     typedef nsTHashtable<CharGlyphMapEntryType> CharGlyphMap;
     CharGlyphMap mCharGlyphCache;
 };
 
 #ifndef ANDROID // not needed on Android, uses the standard gfxFontGroup directly
 class THEBES_API gfxFT2FontGroup : public gfxFontGroup {
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -43,16 +43,17 @@
 #endif
 #include "prlog.h"
 
 #include "nsServiceManagerUtils.h"
 #include "nsReadableUtils.h"
 #include "nsExpirationTracker.h"
 #include "nsILanguageAtomService.h"
 #include "nsIMemoryReporter.h"
+#include "nsITimer.h"
 
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 #include "gfxAtoms.h"
 
 #include "prtypes.h"
 #include "gfxTypes.h"
 #include "nsAlgorithm.h"
@@ -976,16 +977,46 @@ gfxFontCache::Shutdown()
     printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize);
     printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
     printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
     printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
     printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
 #endif
 }
 
+gfxFontCache::gfxFontCache()
+    : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000)
+{
+    mFonts.Init();
+    mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
+    if (mWordCacheExpirationTimer) {
+        mWordCacheExpirationTimer->
+            InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
+                                 SHAPED_WORD_TIMEOUT_SECONDS * 1000,
+                                 nsITimer::TYPE_REPEATING_SLACK);
+    }
+}
+
+gfxFontCache::~gfxFontCache()
+{
+    if (mWordCacheExpirationTimer) {
+        mWordCacheExpirationTimer->Cancel();
+        mWordCacheExpirationTimer = nsnull;
+    }
+
+    // Expire everything that has a zero refcount, so we don't leak them.
+    AgeAllGenerations();
+    // All fonts should be gone.
+    NS_WARN_IF_FALSE(mFonts.Count() == 0,
+                     "Fonts still alive while shutting down gfxFontCache");
+    // Note that we have to delete everything through the expiration
+    // tracker, since there might be fonts not in the hashtable but in
+    // the tracker.
+}
+
 bool
 gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const
 {
     return aKey->mFontEntry == mFont->GetFontEntry() &&
            aKey->mStyle->Equals(*mFont->GetStyle());
 }
 
 already_AddRefed<gfxFont>
@@ -1050,16 +1081,32 @@ gfxFontCache::DestroyFont(gfxFont *aFont
     HashEntry *entry = mFonts.GetEntry(key);
     if (entry && entry->mFont == aFont)
         mFonts.RemoveEntry(key);
     NS_ASSERTION(aFont->GetRefCount() == 0,
                  "Destroying with non-zero ref count!");
     delete aFont;
 }
 
+/*static*/
+PLDHashOperator
+gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData)
+{
+    aHashEntry->mFont->AgeCachedWords();
+    return PL_DHASH_NEXT;
+}
+
+/*static*/
+void
+gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
+{
+    gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
+    cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nsnull);
+}
+
 void
 gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
 {
     mAscent = NS_MAX(mAscent, aOther.mAscent);
     mDescent = NS_MAX(mDescent, aOther.mDescent);
     if (aOtherIsOnLeft) {
         mBoundingBox =
             (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox);
@@ -1091,16 +1138,30 @@ gfxFont::~gfxFont()
     // We destroy the contents of mGlyphExtentsArray explicitly instead of
     // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs
     // of classes that lack a proper copy constructor
     for (i = 0; i < mGlyphExtentsArray.Length(); ++i) {
         delete mGlyphExtentsArray[i];
     }
 }
 
+/*static*/
+PLDHashOperator
+gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
+{
+    if (!aEntry->mShapedWord) {
+        NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!");
+        return PL_DHASH_REMOVE;
+    }
+    if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
+        return PL_DHASH_REMOVE;
+    }
+    return PL_DHASH_NEXT;
+}
+
 hb_blob_t *
 gfxFont::GetFontTable(PRUint32 aTag) {
     hb_blob_t *blob;
     if (mFontEntry->GetExistingFontTable(aTag, &blob))
         return blob;
 
     FallibleTArray<PRUint8> buffer;
     bool haveTable = NS_SUCCEEDED(mFontEntry->GetFontTable(aTag, buffer));
@@ -1192,18 +1253,18 @@ struct GlyphBufferAzure {
 };
 
 // Bug 674909. When synthetic bolding text by drawing twice, need to
 // render using a pixel offset in device pixels, otherwise text
 // doesn't appear bolded, it appears as if a bad text shadow exists
 // when a non-identity transform exists.  Use an offset factor so that
 // the second draw occurs at a constant offset in device pixels.
 
-static double
-CalcXScale(gfxContext *aContext)
+double
+gfxFont::CalcXScale(gfxContext *aContext)
 {
     // determine magnitude of a 1px x offset in device space
     gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0));
     if (t.width == 1.0 && t.height == 0.0) {
         // short-circuit the most common case to avoid sqrt() and division
         return 1.0;
     }
 
@@ -1723,144 +1784,325 @@ gfxFont::Measure(gfxTextRun *aTextRun,
 #define MAX_SHAPING_LENGTH  32760 // slightly less than 32K, trying to avoid
                                   // over-stressing platform shapers
 
 #define BACKTRACK_LIMIT  1024 // If we can't find a space or a cluster start
                               // within 1K chars, just chop arbitrarily.
                               // Limiting backtrack here avoids pathological
                               // behavior on long runs with no whitespace.
 
-bool
-gfxFont::SplitAndInitTextRun(gfxContext *aContext,
-                             gfxTextRun *aTextRun,
-                             const PRUnichar *aString,
-                             PRUint32 aRunStart,
-                             PRUint32 aRunLength,
-                             PRInt32 aRunScript)
+static bool
+IsClusterExtender(PRUint32 aUSV)
+{
+    PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aUSV);
+    return ((category >= HB_CATEGORY_COMBINING_MARK &&
+             category <= HB_CATEGORY_NON_SPACING_MARK) ||
+            (aUSV >= 0x200c && aUSV <= 0x200d) || // ZWJ, ZWNJ
+            (aUSV >= 0xff9e && aUSV <= 0xff9f));  // katakana sound marks
+}
+
+static bool
+IsBoundarySpace(PRUnichar aChar, PRUnichar aNextChar)
+{
+    return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar);
+}
+
+static inline PRUint32
+HashMix(PRUint32 aHash, PRUnichar aCh)
 {
+    return (aHash >> 28) ^ (aHash << 4) ^ aCh;
+}
+
+template<typename T>
+gfxShapedWord*
+gfxFont::GetShapedWord(gfxContext *aContext,
+                       const T *aText,
+                       PRUint32 aLength,
+                       PRUint32 aHash,
+                       PRInt32 aRunScript,
+                       PRInt32 aAppUnitsPerDevUnit,
+                       PRUint32 aFlags)
+{
+    // if there's a cached entry for this word, just return it
+    CacheHashKey key(aText, aLength, aHash,
+                     aRunScript,
+                     aAppUnitsPerDevUnit,
+                     aFlags);
+
+    CacheHashEntry *entry = mWordCache.PutEntry(key);
+    gfxShapedWord *sw = entry->mShapedWord;
+    if (sw) {
+        sw->ResetAge();
+        return sw;
+    }
+
+    sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength,
+                                                    aRunScript,
+                                                    aAppUnitsPerDevUnit,
+                                                    aFlags);
+    NS_ASSERTION(sw != nsnull,
+                 "failed to create gfxShapedWord - expect missing text");
+    if (!sw) {
+        return nsnull;
+    }
+
     bool ok;
-
-#ifdef PR_LOGGING
-    PRLogModuleInfo *log = (mStyle.systemFont ?
-                            gfxPlatform::GetLog(eGfxLog_textrunui) :
-                            gfxPlatform::GetLog(eGfxLog_textrun));
-
-    if (NS_UNLIKELY(log)) {
-        nsCAutoString lang;
-        mStyle.language->ToUTF8String(lang);
-        PR_LOG(log, PR_LOG_DEBUG,\
-               ("(%s-fontmatching) font: [%s] lang: %s script: %d len: %d "
-                "TEXTRUN [%s] ENDTEXTRUN\n",
-                (mStyle.systemFont ? "textrunui" : "textrun"),
-                NS_ConvertUTF16toUTF8(GetName()).get(),
-                lang.get(), aRunScript, aRunLength,
-                NS_ConvertUTF16toUTF8(aString + aRunStart, aRunLength).get()));
-    }
-#endif
-
-    do {
-        // Because various shaping backends struggle with very long runs,
-        // we look for appropriate break locations (preferring whitespace),
-        // and shape sub-runs of no more than 32K characters at a time.
-        // See bug 606714 (CoreText), and similar Uniscribe issues.
-        // This loop always executes at least once, and "processes" up to
-        // MAX_RUN_LENGTH_FOR_SHAPING characters, updating aRunStart and
-        // aRunLength accordingly. It terminates when the entire run has
-        // been processed, or when shaping fails.
-
-        PRUint32 thisRunLength;
-        ok = false;
-
-        if (aRunLength <= MAX_SHAPING_LENGTH) {
-            thisRunLength = aRunLength;
-        } else {
-            // We're splitting this font run because it's very long
-            PRUint32 offset = aRunStart + MAX_SHAPING_LENGTH;
-            PRUint32 clusterStart = 0;
-            while (offset > aRunStart + MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) {
-                if (aTextRun->IsClusterStart(offset)) {
-                    if (!clusterStart) {
-                        clusterStart = offset;
-                    }
-                    if (aString[offset] == ' ' || aString[offset - 1] == ' ') {
-                        break;
-                    }
-                }
-                --offset;
-            }
-            
-            if (offset > MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) {
-                // we found a space, so break the run there
-                thisRunLength = offset - aRunStart;
-            } else if (clusterStart != 0) {
-                // didn't find a space, but we found a cluster start
-                thisRunLength = clusterStart - aRunStart;
-            } else {
-                // otherwise we'll simply break at MAX_SHAPING_LENGTH chars,
-                // which may interfere with shaping behavior (but in practice
-                // only pathological cases will lack ANY whitespace or cluster
-                // boundaries, so we don't really care; it won't affect any
-                // "real" text)
-                thisRunLength = MAX_SHAPING_LENGTH;
-            }
+    if (sizeof(T) == sizeof(PRUnichar)) {
+        ok = ShapeWord(aContext, sw, (const PRUnichar*)aText);
+    } else {
+        nsAutoString utf16;
+        AppendASCIItoUTF16(nsDependentCSubstring((const char*)aText, aLength),
+                           utf16);
+        ok = ShapeWord(aContext, sw, utf16.BeginReading());
+    }
+    NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text");
+
+    for (PRUint32 i = 0; i < aLength; ++i) {
+        if (aText[i] == ' ') {
+            sw->SetIsSpace(i);
+        } else if (i > 0 &&
+                   NS_IS_HIGH_SURROGATE(aText[i - 1]) &&
+                   NS_IS_LOW_SURROGATE(aText[i])) {
+            sw->SetIsLowSurrogate(i);
         }
-
-        ok = InitTextRun(aContext, aTextRun, aString,
-                         aRunStart, thisRunLength, aRunScript);
-
-        aRunStart += thisRunLength;
-        aRunLength -= thisRunLength;
-    } while (ok && aRunLength > 0);
-
-    NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
-    return ok;
+    }
+
+    return sw;
 }
 
 bool
-gfxFont::InitTextRun(gfxContext *aContext,
-                     gfxTextRun *aTextRun,
-                     const PRUnichar *aString,
-                     PRUint32 aRunStart,
-                     PRUint32 aRunLength,
-                     PRInt32 aRunScript,
-                     bool aPreferPlatformShaping)
+gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
+{
+    const gfxShapedWord *sw = mShapedWord;
+    if (!sw) {
+        return false;
+    }
+    if (sw->Length() != aKey->mLength ||
+        sw->Flags() != aKey->mFlags ||
+        sw->AppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
+        sw->Script() != aKey->mScript) {
+        return false;
+    }
+    if (sw->TextIs8Bit()) {
+        if (aKey->mTextIs8Bit) {
+            return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle,
+                                aKey->mLength * sizeof(PRUint8)));
+        }
+        // The key has 16-bit text, even though all the characters are < 256,
+        // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
+        // comparing with will have 8-bit text.
+        const PRUint8   *s1 = sw->Text8Bit();
+        const PRUnichar *s2 = aKey->mText.mDouble;
+        const PRUnichar *s2end = s2 + aKey->mLength;
+        while (s2 < s2end) {
+            if (*s1++ != *s2++) {
+                return false;
+            }
+        }
+        return true;
+    }
+    NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 &&
+                 !aKey->mTextIs8Bit, "didn't expect 8-bit text here");
+    return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
+                        aKey->mLength * sizeof(PRUnichar)));
+}
+
+bool
+gfxFont::ShapeWord(gfxContext *aContext,
+                   gfxShapedWord *aShapedWord,
+                   const PRUnichar *aText,
+                   bool aPreferPlatformShaping)
 {
     bool ok = false;
 
 #ifdef MOZ_GRAPHITE
     if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
-        ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                          aRunStart, aRunLength,
-                                          aRunScript);
+        ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aText);
     }
 #endif
 
     if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) {
-        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) {
-            ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength,
-                                              aRunScript);
+        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) {
+            ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aText);
         }
     }
 
     if (!ok) {
         if (!mPlatformShaper) {
             CreatePlatformShaper();
             NS_ASSERTION(mPlatformShaper, "no platform shaper available!");
         }
         if (mPlatformShaper) {
-            ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength,
-                                              aRunScript);
+            ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aText);
         }
     }
 
+    if (ok && IsSyntheticBold()) {
+        float synBoldOffset =
+                GetSyntheticBoldOffset() * CalcXScale(aContext);
+        aShapedWord->AdjustAdvancesForSyntheticBold(synBoldOffset);
+    }
+
     return ok;
 }
 
+template<typename T>
+bool
+gfxFont::SplitAndInitTextRun(gfxContext *aContext,
+                             gfxTextRun *aTextRun,
+                             const T *aString,
+                             PRUint32 aRunStart,
+                             PRUint32 aRunLength,
+                             PRInt32 aRunScript)
+{
+    if (aRunLength == 0) {
+        return true;
+    }
+
+    InitWordCache();
+
+    // the only flags we care about for ShapedWord construction/caching
+    PRUint32 flags = aTextRun->GetFlags() &
+        (gfxTextRunFactory::TEXT_IS_RTL |
+         gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES);
+    if (sizeof(T) == sizeof(PRUint8)) {
+        flags |= gfxTextRunFactory::TEXT_IS_8BIT;
+    }
+
+    const T *text = aString + aRunStart;
+    PRUint32 wordStart = 0;
+    PRUint32 hash = 0;
+    bool wordIs8Bit = true;
+    PRInt32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+
+    T nextCh = text[0];
+    for (PRUint32 i = 0; i <= aRunLength; ++i) {
+        T ch = nextCh;
+        nextCh = (i < aRunLength - 1) ? text[i + 1] : '\n';
+        bool boundary = IsBoundarySpace(ch, nextCh);
+        bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
+        PRUint32 length = i - wordStart;
+
+        // break into separate ShapedWords when we hit an invalid char,
+        // or a boundary space (always handled individually),
+        // or the first non-space after a space
+        bool breakHere = boundary || invalid;
+
+        if (!breakHere) {
+            // if we're approaching the max ShapedWord length, break anyway...
+            if (sizeof(T) == sizeof(PRUint8)) {
+                // in 8-bit text, no clusters or surrogates to worry about
+                if (length >= gfxShapedWord::kMaxLength) {
+                    breakHere = true;
+                }
+            } else {
+                // try to avoid breaking before combining mark or low surrogate
+                if (length >= gfxShapedWord::kMaxLength - 15) {
+                    if (!NS_IS_LOW_SURROGATE(ch)) {
+                        if (!IsClusterExtender(ch)) {
+                            breakHere = true;
+                        }
+                    }
+                    if (!breakHere && length >= gfxShapedWord::kMaxLength - 3) {
+                        if (!NS_IS_LOW_SURROGATE(ch)) {
+                            breakHere = true;
+                        }
+                    }
+                    if (!breakHere && length >= gfxShapedWord::kMaxLength) {
+                        breakHere = true;
+                    }
+                }
+            }
+        }
+
+        if (!breakHere) {
+            if (ch >= 0x100) {
+                wordIs8Bit = false;
+            }
+            // include this character in the hash, and move on to next
+            hash = HashMix(hash, ch);
+            continue;
+        }
+
+        // We've decided to break here (i.e. we're at the end of a "word",
+        // or the word is becoming excessively long): shape the word and
+        // add it to the textrun
+        if (length > 0) {
+            PRUint32 wordFlags = flags;
+            // in the 8-bit version of this method, TEXT_IS_8BIT was
+            // already set as part of |flags|, so no need for a per-word
+            // adjustment here
+            if (sizeof(T) == sizeof(PRUnichar)) {
+                if (wordIs8Bit) {
+                    wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
+                }
+            }
+            gfxShapedWord *sw = GetShapedWord(aContext,
+                                              text + wordStart, length,
+                                              hash, aRunScript,
+                                              appUnitsPerDevUnit,
+                                              wordFlags);
+            if (sw) {
+                aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
+            } else {
+                return false; // failed, presumably out of memory?
+            }
+        }
+
+        if (boundary) {
+            // word was terminated by a space: add that to the textrun
+            if (!aTextRun->SetSpaceGlyphIfSimple(this, aContext,
+                                                 aRunStart + i, ch))
+            {
+                static const PRUint8 space = ' ';
+                gfxShapedWord *sw =
+                    GetShapedWord(aContext,
+                                  &space, 1,
+                                  HashMix(0, ' '), aRunScript,
+                                  appUnitsPerDevUnit,
+                                  flags | gfxTextRunFactory::TEXT_IS_8BIT);
+                if (sw) {
+                    aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
+                } else {
+                    return false;
+                }
+            }
+            hash = 0;
+            wordStart = i + 1;
+            wordIs8Bit = true;
+            continue;
+        }
+
+        if (i == aRunLength) {
+            break;
+        }
+
+        if (invalid) {
+            // word was terminated by an invalid char: skip it,
+            // but record where TAB or NEWLINE occur
+            if (ch == '\t') {
+                aTextRun->SetIsTab(aRunStart + i);
+            } else if (ch == '\n') {
+                aTextRun->SetIsNewline(aRunStart + i);
+            }
+            hash = 0;
+            wordStart = i + 1;
+            wordIs8Bit = true;
+            continue;
+        }
+
+        // word was forcibly broken, so current char will begin next word
+        hash = HashMix(0, ch);
+        wordStart = i;
+        wordIs8Bit = (ch < 0x100);
+    }
+
+    return true;
+}
+
 gfxGlyphExtents *
 gfxFont::GetOrCreateGlyphExtents(PRUint32 aAppUnitsPerDevUnit) {
     PRUint32 i;
     for (i = 0; i < mGlyphExtentsArray.Length(); ++i) {
         if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
             return mGlyphExtentsArray[i];
     }
     gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
@@ -2425,28 +2667,35 @@ gfxFontGroup::~gfxFontGroup() {
 
 gfxFontGroup *
 gfxFontGroup::Copy(const gfxFontStyle *aStyle)
 {
     return new gfxFontGroup(mFamilies, aStyle, mUserFontSet);
 }
 
 bool 
-gfxFontGroup::IsInvalidChar(PRUnichar ch) {
-    if (ch >= 32) {
-        return ch == 0x0085/*NEL*/ ||
-            ((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
-             (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ ||
-              IS_BIDI_CONTROL_CHAR(ch)));
-    }
-    // We could just blacklist all control characters, but it seems better
-    // to only blacklist the ones we know cause problems for native font
-    // engines.
-    return ch == 0x0B || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f' ||
-        (ch >= 0x1c && ch <= 0x1f);
+gfxFontGroup::IsInvalidChar(PRUint8 ch)
+{
+    return ((ch & 0x7f) < 0x20);
+}
+
+bool 
+gfxFontGroup::IsInvalidChar(PRUnichar ch)
+{
+    // All printable 7-bit ASCII values are OK
+    if (ch >= ' ' && ch < 0x80) {
+        return false;
+    }
+    // No point in sending non-printing control chars through font shaping
+    if (ch <= 0x9f) {
+        return true;
+    }
+    return ((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
+         (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ ||
+          IS_BIDI_CONTROL_CHAR(ch)));
 }
 
 bool
 gfxFontGroup::ForEachFont(FontCreationCallback fc,
                           void *closure)
 {
     return ForEachFontInternal(mFamilies, mStyle.language,
                                true, true, true, fc, closure);
@@ -2648,214 +2897,308 @@ gfxFontGroup::MakeEmptyTextRun(const Par
 }
 
 gfxTextRun *
 gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags)
 {
     aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
     static const PRUint8 space = ' ';
 
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags);
-    if (!textRun)
+    gfxTextRun *textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags);
+    if (!textRun) {
         return nsnull;
+    }
 
     gfxFont *font = GetFontAt(0);
     if (NS_UNLIKELY(GetStyle()->size == 0)) {
         // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
         // them, and always create at least size 1 fonts, i.e. they still
         // render something for size 0 fonts.
         textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false);
     }
     else {
         textRun->SetSpaceGlyph(font, aParams->mContext, 0);
     }
+
     // Note that the gfxGlyphExtents glyph bounds storage for the font will
     // always contain an entry for the font's space glyph, so we don't have
     // to call FetchGlyphExtents here.
-    return textRun.forget();
-}
-
-#define UNICODE_LRO 0x202d
-#define UNICODE_RLO 0x202e
-#define UNICODE_PDF 0x202c
-
-inline void
-AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString)
-{
-    static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO };
-    aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]);    
-    aString.Append(' ');
+    return textRun;
 }
 
-inline void
-AppendDirectionalIndicatorEnd(bool aNeedDirection, nsAString& aString)
+gfxTextRun *
+gfxFontGroup::MakeBlankTextRun(const void* aText, PRUint32 aLength,
+                               const Parameters *aParams, PRUint32 aFlags)
 {
-    // append a space (always, for consistent treatment of last char,
-    // and a direction control if required (we skip this for 8-bit text,
-    // which is known to be unidirectional LTR, unless the direction was
-    // forced RTL via overrides)
-    aString.Append(' ');
-    if (!aNeedDirection)
-        return;
-
-    aString.Append('.');
-    aString.Append(UNICODE_PDF);
+    gfxTextRun *textRun =
+        gfxTextRun::Create(aParams, aText, aLength, this, aFlags);
+    if (!textRun) {
+        return nsnull;
+    }
+
+    textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false);
+    return textRun;
 }
 
 gfxTextRun *
 gfxFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
                           const Parameters *aParams, PRUint32 aFlags)
 {
-    NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text");
-    NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit");
-    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
-    if (!textRun)
+    if (aLength == 0) {
+        return MakeEmptyTextRun(aParams, aFlags);
+    }
+    if (aLength == 1 && aString[0] == ' ') {
+        return MakeSpaceTextRun(aParams, aFlags);
+    }
+
+    aFlags |= TEXT_IS_8BIT;
+
+    if (GetStyle()->size == 0) {
+        // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
+        // them, and always create at least size 1 fonts, i.e. they still
+        // render something for size 0 fonts.
+        return MakeBlankTextRun(aString, aLength, aParams, aFlags);
+    }
+
+    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength,
+                                             this, aFlags);
+    if (!textRun) {
         return nsnull;
-
-    nsDependentCSubstring cString(reinterpret_cast<const char*>(aString),
-                                  reinterpret_cast<const char*>(aString) + aLength);
-
-    nsAutoString utf16;
-    AppendASCIItoUTF16(cString, utf16);
-
-    InitTextRun(aParams->mContext, textRun, utf16.get(), utf16.Length());
+    }
+
+    InitTextRun(aParams->mContext, textRun, aString, aLength);
 
     textRun->FetchGlyphExtents(aParams->mContext);
 
     return textRun;
 }
 
 gfxTextRun *
 gfxFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
                           const Parameters *aParams, PRUint32 aFlags)
 {
-    NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text");
-    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
-    if (!textRun)
+    if (aLength == 0) {
+        return MakeEmptyTextRun(aParams, aFlags);
+    }
+    if (aLength == 1 && aString[0] == ' ') {
+        return MakeSpaceTextRun(aParams, aFlags);
+    }
+    if (GetStyle()->size == 0) {
+        return MakeBlankTextRun(aString, aLength, aParams, aFlags);
+    }
+
+    gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength,
+                                             this, aFlags);
+    if (!textRun) {
         return nsnull;
-
-    gfxPlatform::GetPlatform()->SetupClusterBoundaries(textRun, aString);
+    }
 
     InitTextRun(aParams->mContext, textRun, aString, aLength);
 
     textRun->FetchGlyphExtents(aParams->mContext);
 
     return textRun;
 }
 
+template<typename T>
 void
 gfxFontGroup::InitTextRun(gfxContext *aContext,
                           gfxTextRun *aTextRun,
-                          const PRUnichar *aString,
+                          const T *aString,
                           PRUint32 aLength)
 {
-    // split into script runs so that script can potentially influence
-    // the font matching process below
-    gfxScriptItemizer scriptRuns(aString, aLength);
-
-#ifdef PR_LOGGING
-    PRLogModuleInfo *log = (mStyle.systemFont ?
-                            gfxPlatform::GetLog(eGfxLog_textrunui) :
-                            gfxPlatform::GetLog(eGfxLog_textrun));
-#endif
-
-    PRUint32 runStart = 0, runLimit = aLength;
-    PRInt32 runScript = HB_SCRIPT_LATIN;
-    while (scriptRuns.Next(runStart, runLimit, runScript)) {
+    // we need to do numeral processing even on 8-bit text,
+    // in case we're converting Western to Hindi/Arabic digits
+    PRInt32 numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
+    nsAutoArrayPtr<PRUnichar> transformedString;
+    if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
+        // scan the string for numerals that may need to be transformed;
+        // if we find any, we'll make a local copy here and use that for
+        // font matching and glyph generation/shaping
+        bool prevIsArabic =
+            (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0;
+        for (PRUint32 i = 0; i < aLength; ++i) {
+            PRUnichar origCh = aString[i];
+            PRUnichar newCh = HandleNumberInChar(origCh, prevIsArabic, numOption);
+            if (newCh != origCh) {
+                if (!transformedString) {
+                    transformedString = new PRUnichar[aLength];
+                    if (sizeof(T) == sizeof(PRUnichar)) {
+                        memcpy(transformedString.get(), aString, i * sizeof(PRUnichar));
+                    } else {
+                        for (PRUint32 j = 0; j < i; ++j) {
+                            transformedString[j] = aString[j];
+                        }
+                    }
+                }
+            }
+            if (transformedString) {
+                transformedString[i] = newCh;
+            }
+            prevIsArabic = IS_ARABIC_CHAR(newCh);
+        }
+    }
+
+    if (sizeof(T) == sizeof(PRUint8) && !transformedString) {
+        // the text is still purely 8-bit; bypass the script-run itemizer
+        // and treat it as a single Latin run
+        InitScriptRun(aContext, aTextRun, aString,
+                      0, aLength, HB_SCRIPT_LATIN);
+    } else {
+        const PRUnichar *textPtr;
+        if (transformedString) {
+            textPtr = transformedString.get();
+        } else {
+            // typecast to avoid compilation error for the 8-bit version,
+            // even though this is dead code in that case
+            textPtr = reinterpret_cast<const PRUnichar*>(aString);
+        }
+
+        // split into script runs so that script can potentially influence
+        // the font matching process below
+        gfxScriptItemizer scriptRuns(textPtr, aLength);
 
 #ifdef PR_LOGGING
-        if (NS_UNLIKELY(log)) {
-            nsCAutoString lang;
-            mStyle.language->ToUTF8String(lang);
-            PRUint32 runLen = runLimit - runStart;
-            PR_LOG(log, PR_LOG_DEBUG,\
-                   ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
-                    "weight: %d width: %d style: %s "
-                    "TEXTRUN [%s] ENDTEXTRUN\n",
-                    (mStyle.systemFont ? "textrunui" : "textrun"),
-                    NS_ConvertUTF16toUTF8(mFamilies).get(),
-                    lang.get(), runScript, runLen,
-                    PRUint32(mStyle.weight), PRUint32(mStyle.stretch),
-                    (mStyle.style & FONT_STYLE_ITALIC ? "italic" :
-                    (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" :
-                                                            "normal")),
-                    NS_ConvertUTF16toUTF8(aString + runStart, runLen).get()));
+        PRLogModuleInfo *log = (mStyle.systemFont ?
+                                gfxPlatform::GetLog(eGfxLog_textrunui) :
+                                gfxPlatform::GetLog(eGfxLog_textrun));
+#endif
+
+        PRUint32 runStart = 0, runLimit = aLength;
+        PRInt32 runScript = HB_SCRIPT_LATIN;
+        while (scriptRuns.Next(runStart, runLimit, runScript)) {
+
+#ifdef PR_LOGGING
+            if (NS_UNLIKELY(log)) {
+                nsCAutoString lang;
+                mStyle.language->ToUTF8String(lang);
+                PRUint32 runLen = runLimit - runStart;
+                PR_LOG(log, PR_LOG_DEBUG,\
+                       ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
+                        "weight: %d width: %d style: %s "
+                        "TEXTRUN [%s] ENDTEXTRUN\n",
+                        (mStyle.systemFont ? "textrunui" : "textrun"),
+                        NS_ConvertUTF16toUTF8(mFamilies).get(),
+                        lang.get(), runScript, runLen,
+                        PRUint32(mStyle.weight), PRUint32(mStyle.stretch),
+                        (mStyle.style & FONT_STYLE_ITALIC ? "italic" :
+                        (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" :
+                                                                "normal")),
+                        NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+            }
+#endif
+
+            InitScriptRun(aContext, aTextRun, textPtr,
+                          runStart, runLimit, runScript);
         }
-#endif
-
-        InitScriptRun(aContext, aTextRun, aString, aLength,
-                      runStart, runLimit, runScript);
+    }
+
+    if (sizeof(T) == sizeof(PRUnichar) && aLength > 0) {
+        gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs();
+        if (!glyph->IsSimpleGlyph()) {
+            glyph->SetClusterStart(true);
+        }
     }
 
     // It's possible for CoreText to omit glyph runs if it decides they contain
     // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we
     // need to eliminate them from the glyph run array to avoid drawing "partial
     // ligatures" with the wrong font.
     // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because
     // it will iterate back over all glyphruns in the textrun, which leads to
     // pathologically-bad perf in the case where a textrun contains many script
     // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
     // every time a new script subrun is processed.
     aTextRun->SanitizeGlyphRuns();
 
     aTextRun->SortGlyphRuns();
 }
 
+template<typename T>
 void
 gfxFontGroup::InitScriptRun(gfxContext *aContext,
                             gfxTextRun *aTextRun,
-                            const PRUnichar *aString,
-                            PRUint32 aTotalLength,
+                            const T *aString,
                             PRUint32 aScriptRunStart,
                             PRUint32 aScriptRunEnd,
                             PRInt32 aRunScript)
 {
-    gfxFont *mainFont = mFonts[0].get();
+    gfxFont *mainFont = GetFontAt(0);
 
     PRUint32 runStart = aScriptRunStart;
     nsAutoTArray<gfxTextRange,3> fontRanges;
-    ComputeRanges(fontRanges, aString,
-                  aScriptRunStart, aScriptRunEnd, aRunScript);
+    ComputeRanges(fontRanges, aString + aScriptRunStart,
+                  aScriptRunEnd - aScriptRunStart, aRunScript);
     PRUint32 numRanges = fontRanges.Length();
 
     for (PRUint32 r = 0; r < numRanges; r++) {
         const gfxTextRange& range = fontRanges[r];
         PRUint32 matchedLength = range.Length();
         gfxFont *matchedFont = (range.font ? range.font.get() : nsnull);
 
         // create the glyph run for this range
         if (matchedFont) {
             aTextRun->AddGlyphRun(matchedFont, range.matchType,
                                   runStart, (matchedLength > 0));
-        } else {
-            aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup,
-                                  runStart, (matchedLength > 0));
-        }
-        if (matchedFont) {
             // do glyph layout and record the resulting positioned glyphs
             if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString,
                                                   runStart, matchedLength,
                                                   aRunScript)) {
                 // glyph layout failed! treat as missing glyphs
                 matchedFont = nsnull;
             }
+        } else {
+            aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup,
+                                  runStart, (matchedLength > 0));
         }
+
         if (!matchedFont) {
-            for (PRUint32 index = runStart; index < runStart + matchedLength; index++) {
-                // Record the char code so we can draw a box with the Unicode value
-                PRUint32 ch = aString[index];
-                if (NS_IS_HIGH_SURROGATE(ch) &&
-                    index + 1 < aScriptRunEnd &&
-                    NS_IS_LOW_SURROGATE(aString[index+1])) {
-                    aTextRun->SetMissingGlyph(index,
-                                              SURROGATE_TO_UCS4(aString[index],
-                                                                aString[index+1]));
-                    index++;
-                } else {
+            // for PRUnichar text, we need to set cluster boundaries so that
+            // surrogate pairs, combining characters, etc behave properly,
+            // even if we don't have glyphs for them
+            if (sizeof(T) == sizeof(PRUnichar)) {
+                gfxShapedWord::SetupClusterBoundaries(aTextRun->GetCharacterGlyphs() + runStart,
+                                                      reinterpret_cast<const PRUnichar*>(aString) + runStart,
+                                                      matchedLength);
+            }
+
+            // various "missing" characters may need special handling,
+            // so we check for them here
+            PRUint32 runLimit = runStart + matchedLength;
+            for (PRUint32 index = runStart; index < runLimit; index++) {
+                T ch = aString[index];
+
+                // tab and newline are not to be displayed as hexboxes,
+                // but do need to be recorded in the textrun
+                if (ch == '\n') {
+                    aTextRun->SetIsNewline(index);
+                    continue;
+                }
+                if (ch == '\t') {
+                    aTextRun->SetIsTab(index);
+                    continue;
+                }
+
+                // for 16-bit textruns only, check for surrogate pairs and
+                // special Unicode spaces; omit these checks in 8-bit runs
+                if (sizeof(T) == sizeof(PRUnichar)) {
+                    if (NS_IS_HIGH_SURROGATE(ch) &&
+                        index + 1 < aScriptRunEnd &&
+                        NS_IS_LOW_SURROGATE(aString[index + 1]))
+                    {
+                        aTextRun->SetMissingGlyph(index,
+                                                  SURROGATE_TO_UCS4(ch,
+                                                                    aString[index + 1]));
+                        index++;
+                        aTextRun->SetIsLowSurrogate(index);
+                        continue;
+                    }
+
+                    // check if this is a known Unicode whitespace character that
+                    // we can render using the space glyph with a custom width
                     gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
                     if (wid >= 0.0) {
                         nscoord advance =
                             aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5);
                         gfxTextRun::CompressedGlyph g;
                         if (gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance)) {
                             aTextRun->SetSimpleGlyph(index,
                                                      g.SetSimpleGlyph(advance,
@@ -2864,28 +3207,34 @@ gfxFontGroup::InitScriptRun(gfxContext *
                             gfxTextRun::DetailedGlyph detailedGlyph;
                             detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
                             detailedGlyph.mAdvance = advance;
                             detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
                             g.SetComplex(true, true, 1);
                             aTextRun->SetGlyphs(index,
                                                 g, &detailedGlyph);
                         }
-                    } else {
-                        aTextRun->SetMissingGlyph(index, ch);
+                        continue;
                     }
                 }
+
+                if (IsInvalidChar(ch)) {
+                    // invalid chars are left as zero-width/invisible
+                    continue;
+                }
+
+                // record char code so we can draw a box with the Unicode value
+                aTextRun->SetMissingGlyph(index, ch);
             }
         }
 
         runStart += matchedLength;
     }
 }
 
-
 already_AddRefed<gfxFont>
 gfxFontGroup::FindFontForChar(PRUint32 aCh, PRUint32 aPrevCh,
                               PRInt32 aRunScript, gfxFont *aPrevMatchedFont,
                               PRUint8 *aMatchType)
 {
     nsRefPtr<gfxFont>    selectedFont;
 
     // if this character is a join-control or the previous is a join-causer,
@@ -2950,44 +3299,49 @@ gfxFontGroup::FindFontForChar(PRUint32 a
         *aMatchType = gfxTextRange::kSystemFallback;
         selectedFont = WhichSystemFontSupportsChar(aCh);
         return selectedFont.forget();
     }
 
     return nsnull;
 }
 
-
+template<typename T>
 void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges,
-                                 const PRUnichar *aString,
-                                 PRUint32 begin, PRUint32 end,
+                                 const T *aString, PRUint32 aLength,
                                  PRInt32 aRunScript)
 {
-    const PRUnichar *str = aString + begin;
-    PRUint32 len = end - begin;
-
     aRanges.Clear();
 
-    if (len == 0) {
+    if (aLength == 0) {
         return;
     }
 
     PRUint32 prevCh = 0;
     gfxFont *prevFont = nsnull;
     PRUint8 matchType = 0;
 
-    for (PRUint32 i = 0; i < len; i++) {
+    for (PRUint32 i = 0; i < aLength; i++) {
 
         const PRUint32 origI = i; // save off in case we increase for surrogate
 
         // set up current ch
-        PRUint32 ch = str[i];
-        if ((i+1 < len) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(str[i+1])) {
-            i++;
-            ch = SURROGATE_TO_UCS4(ch, str[i]);
+        PRUint32 ch = aString[i];
+
+        // in 16-bit case only, check for surrogate pair
+        if (sizeof(T) == sizeof(PRUnichar)) {
+            if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) &&
+                                 NS_IS_LOW_SURROGATE(aString[i + 1])) {
+                i++;
+                ch = SURROGATE_TO_UCS4(ch, aString[i]);
+            }
+        }
+
+        if (ch == 0xa0) {
+            ch = ' ';
         }
 
         // find the font for this char
         nsRefPtr<gfxFont> font =
             FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType);
 
         prevCh = ch;
 
@@ -2996,28 +3350,31 @@ void gfxFontGroup::ComputeRanges(nsTArra
             aRanges.AppendElement(gfxTextRange(0, 1, font, matchType));
             prevFont = font;
         } else {
             // if font has changed, make a new range
             gfxTextRange& prevRange = aRanges[aRanges.Length() - 1];
             if (prevRange.font != font || prevRange.matchType != matchType) {
                 // close out the previous range
                 prevRange.end = origI;
-                aRanges.AppendElement(gfxTextRange(origI, i+1, font, matchType));
+                aRanges.AppendElement(gfxTextRange(origI, i + 1,
+                                                   font, matchType));
 
                 // update prevFont for the next match, *unless* we switched
                 // fonts on a ZWJ, in which case propagating the changed font
                 // is probably not a good idea (see bug 619511)
-                if (!gfxFontUtils::IsJoinCauser(ch)) {
+                if (sizeof(T) == sizeof(PRUint8) ||
+                    !gfxFontUtils::IsJoinCauser(ch))
+                {
                     prevFont = font;
                 }
             }
         }
     }
-    aRanges[aRanges.Length()-1].end = len;
+    aRanges[aRanges.Length() - 1].end = aLength;
 }
 
 gfxUserFontSet* 
 gfxFontGroup::GetUserFontSet()
 {
     return mUserFontSet;
 }
 
@@ -3301,16 +3658,240 @@ gfxFontStyle::ComputeWeight() const
     if (baseWeight < 0)
         baseWeight = 0;
     if (baseWeight > 9)
         baseWeight = 9;
 
     return baseWeight;
 }
 
+// This is not a member function of gfxShapedWord because it is also used
+// by gfxFontGroup on missing-glyph runs, where we don't actually "shape"
+// anything but still need to set cluster info.
+/*static*/ void
+gfxShapedWord::SetupClusterBoundaries(CompressedGlyph *aGlyphs,
+                                      const PRUnichar *aString, PRUint32 aLength)
+{
+    gfxTextRun::CompressedGlyph extendCluster;
+    extendCluster.SetComplex(false, true, 0);
+
+    gfxUnicodeProperties::HSType hangulState = gfxUnicodeProperties::HST_NONE;
+
+    for (PRUint32 i = 0; i < aLength; ++i) {
+        bool surrogatePair = false;
+        PRUint32 ch = aString[i];
+        if (NS_IS_HIGH_SURROGATE(ch) &&
+            i < aLength - 1 && NS_IS_LOW_SURROGATE(aString[i+1]))
+        {
+            ch = SURROGATE_TO_UCS4(ch, aString[i+1]);
+            surrogatePair = true;
+        }
+
+        PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(ch);
+        gfxUnicodeProperties::HSType hangulType = gfxUnicodeProperties::HST_NONE;
+
+        // combining marks extend the cluster
+        if (IsClusterExtender(ch)) {
+            aGlyphs[i] = extendCluster;
+        } else if (category == HB_CATEGORY_OTHER_LETTER) {
+            // handle special cases in Letter_Other category
+#if 0
+            // Currently disabled. This would follow the UAX#29 specification
+            // for extended grapheme clusters, but this is not favored by
+            // Thai users, at least for editing behavior.
+            // See discussion of equivalent Pango issue in bug 474068 and
+            // upstream at https://bugzilla.gnome.org/show_bug.cgi?id=576156.
+
+            if ((ch & ~0xff) == 0x0e00) {
+                // specific Thai & Lao (U+0Exx) chars that extend the cluster
+                if ( ch == 0x0e30 ||
+                    (ch >= 0x0e32 && ch <= 0x0e33) ||
+                     ch == 0x0e45 ||
+                     ch == 0x0eb0 ||
+                    (ch >= 0x0eb2 && ch <= 0x0eb3))
+                {
+                    if (i > 0) {
+                        aTextRun->SetGlyphs(i, extendCluster, nsnull);
+                    }
+                }
+                else if ((ch >= 0x0e40 && ch <= 0x0e44) ||
+                         (ch >= 0x0ec0 && ch <= 0x0ec4))
+                {
+                    // characters that are prepended to the following cluster
+                    if (i < length - 1) {
+                        aTextRun->SetGlyphs(i+1, extendCluster, nsnull);
+                    }
+                }
+            } else
+#endif
+            if ((ch & ~0xff) == 0x1100 ||
+                (ch >= 0xa960 && ch <= 0xa97f) ||
+                (ch >= 0xac00 && ch <= 0xd7ff))
+            {
+                // no break within Hangul syllables
+                hangulType = gfxUnicodeProperties::GetHangulSyllableType(ch);
+                switch (hangulType) {
+                case gfxUnicodeProperties::HST_L:
+                case gfxUnicodeProperties::HST_LV:
+                case gfxUnicodeProperties::HST_LVT:
+                    if (hangulState == gfxUnicodeProperties::HST_L) {
+                        aGlyphs[i] = extendCluster;
+                    }
+                    break;
+                case gfxUnicodeProperties::HST_V:
+                    if ( (hangulState != gfxUnicodeProperties::HST_NONE) &&
+                        !(hangulState & gfxUnicodeProperties::HST_T))
+                    {
+                        aGlyphs[i] = extendCluster;
+                    }
+                    break;
+                case gfxUnicodeProperties::HST_T:
+                    if (hangulState & (gfxUnicodeProperties::HST_V |
+                                       gfxUnicodeProperties::HST_T))
+                    {
+                        aGlyphs[i] = extendCluster;
+                    }
+                    break;
+                default:
+                    break;
+                }
+            }
+        }
+
+        if (surrogatePair) {
+            ++i;
+            aGlyphs[i] = extendCluster;
+        }
+
+        hangulState = hangulType;
+    }
+}
+
+gfxShapedWord::DetailedGlyph *
+gfxShapedWord::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount)
+{
+    NS_ASSERTION(aIndex < Length(), "Index out of range");
+
+    if (!mDetailedGlyphs) {
+        mDetailedGlyphs = new DetailedGlyphStore();
+    }
+
+    DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount);
+    if (!details) {
+        mCharacterGlyphs[aIndex].SetMissing(0);
+        return nsnull;
+    }
+
+    return details;
+}
+
+void
+gfxShapedWord::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph,
+                         const DetailedGlyph *aGlyphs)
+{
+    NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
+    NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+                 "First character can't be a ligature continuation!");
+
+    PRUint32 glyphCount = aGlyph.GetGlyphCount();
+    if (glyphCount > 0) {
+        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
+        if (!details) {
+            return;
+        }
+        memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
+    }
+    mCharacterGlyphs[aIndex] = aGlyph;
+}
+
+#include "ignorable.x-ccmap"
+DEFINE_X_CCMAP(gIgnorableCCMapExt, const);
+
+static inline bool
+IsDefaultIgnorable(PRUint32 aChar)
+{
+    return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar);
+}
+
+void
+gfxShapedWord::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar, gfxFont *aFont)
+{
+    DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+    if (!details) {
+        return;
+    }
+
+    details->mGlyphID = aChar;
+    if (IsDefaultIgnorable(aChar)) {
+        // Setting advance width to zero will prevent drawing the hexbox
+        details->mAdvance = 0;
+    } else {
+        gfxFloat width = NS_MAX(aFont->GetMetrics().aveCharWidth,
+                                gfxFontMissingGlyphs::GetDesiredMinWidth(aChar));
+        details->mAdvance = PRUint32(width * mAppUnitsPerDevUnit);
+    }
+    details->mXOffset = 0;
+    details->mYOffset = 0;
+    mCharacterGlyphs[aIndex].SetMissing(1);
+}
+
+bool
+gfxShapedWord::FilterIfIgnorable(PRUint32 aIndex)
+{
+    PRUint32 ch = GetCharAt(aIndex);
+    if (IsDefaultIgnorable(ch)) {
+        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+        if (details) {
+            details->mGlyphID = ch;
+            details->mAdvance = 0;
+            details->mXOffset = 0;
+            details->mYOffset = 0;
+            mCharacterGlyphs[aIndex].SetMissing(1);
+            return true;
+        }
+    }
+    return false;
+}
+
+void
+gfxShapedWord::AdjustAdvancesForSyntheticBold(float aSynBoldOffset)
+{
+    PRUint32 synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit;
+    for (PRUint32 i = 0; i < Length(); ++i) {
+         CompressedGlyph *glyphData = &mCharacterGlyphs[i];
+         if (glyphData->IsSimpleGlyph()) {
+             // simple glyphs ==> just add the advance
+             PRInt32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
+             if (CompressedGlyph::IsSimpleAdvance(advance)) {
+                 glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
+             } else {
+                 // rare case, tested by making this the default
+                 PRUint32 glyphIndex = glyphData->GetSimpleGlyph();
+                 glyphData->SetComplex(true, true, 1);
+                 DetailedGlyph detail = {glyphIndex, advance, 0, 0};
+                 SetGlyphs(i, *glyphData, &detail);
+             }
+         } else {
+             // complex glyphs ==> add offset at cluster/ligature boundaries
+             PRUint32 detailedLength = glyphData->GetGlyphCount();
+             if (detailedLength) {
+                 DetailedGlyph *details = GetDetailedGlyphs(i);
+                 if (!details) {
+                     continue;
+                 }
+                 if (IsRightToLeft()) {
+                     details[0].mAdvance += synAppUnitOffset;
+                 } else {
+                     details[detailedLength - 1].mAdvance += synAppUnitOffset;
+                 }
+             }
+         }
+    }
+}
+
 bool
 gfxTextRun::GlyphRunIterator::NextRun()  {
     if (mNextIndex >= mTextRun->mGlyphRuns.Length())
         return false;
     mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex];
     if (mGlyphRun->mCharacterOffset >= mEndOffset)
         return false;
 
@@ -3330,169 +3911,112 @@ AccountStorageForTextRun(gfxTextRun *aTe
     // Ignores detailed glyphs... we don't know when those have been constructed
     // Also ignores gfxSkipChars dynamic storage (which won't be anything
     // for preformatted text)
     // Also ignores GlyphRun array, again because it hasn't been constructed
     // by the time this gets called. If there's only one glyphrun that's stored
     // directly in the textrun anyway so no additional overhead.
     PRUint32 length = aTextRun->GetLength();
     PRInt32 bytes = length * sizeof(gfxTextRun::CompressedGlyph);
-    if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_PERSISTENT) {
-      bytes += length * ((aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) ? 1 : 2);
-      bytes += sizeof(gfxTextRun::CompressedGlyph) - 1;
-      bytes &= ~(sizeof(gfxTextRun::CompressedGlyph) - 1);
-    }
     bytes += sizeof(gfxTextRun);
     gTextRunStorage += bytes*aSign;
     gTextRunStorageHighWaterMark = NS_MAX(gTextRunStorageHighWaterMark, gTextRunStorage);
 }
 #endif
 
-static PRUint64
-GlyphStorageAllocCount(PRUint32 aLength, PRUint32 aFlags)
-{
-    // always need to allocate storage for the glyph data
-    PRUint64 allocCount = aLength;
-
-    // if the text is not persistent, we also need space for a copy
-    if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) {
-        // figure out number of extra CompressedGlyph elements we need to
-        // get sufficient space for the text
-        typedef gfxTextRun::CompressedGlyph CompressedGlyph;
-        if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
-            allocCount += (aLength + sizeof(CompressedGlyph) - 1) /
-                          sizeof(CompressedGlyph);
-        } else {
-            allocCount += (aLength * sizeof(PRUnichar) +
-                              sizeof(CompressedGlyph) - 1) /
-                          sizeof(CompressedGlyph);
-        }
-    }
-    return allocCount;
-}
-
-// Helper for textRun creation to preallocate storage for glyphs and text;
-// this function returns a pointer to the newly-allocated glyph storage,
-// AND modifies the aText parameter if TEXT_IS_PERSISTENT was not set.
-// In that case, the text is appended to the glyph storage, so a single
-// delete[] operation in the textRun destructor will free both.
+// Helper for textRun creation to preallocate storage for glyph records;
+// this function returns a pointer to the newly-allocated glyph storage.
 // Returns nsnull if allocation fails.
-gfxTextRun::CompressedGlyph *
-gfxTextRun::AllocateStorage(const void*& aText, PRUint32 aLength, PRUint32 aFlags)
+void *
+gfxTextRun::AllocateStorageForTextRun(size_t aSize, PRUint32 aLength)
 {
-    // Here, we rely on CompressedGlyph being the largest unit we care about for
-    // allocation/alignment of either glyph data or text, so we allocate an array
-    // of CompressedGlyphs, then take the last chunk of that and cast a pointer to
-    // PRUint8* or PRUnichar* for text storage.
-
-    PRUint64 allocCount = GlyphStorageAllocCount(aLength, aFlags);
-
-    // allocate the storage we need, returning nsnull on failure rather than
-    // throwing an exception (because web content can create huge runs)
-    CompressedGlyph *storage = new (std::nothrow) CompressedGlyph[allocCount];
+    // Allocate the storage we need, returning nsnull on failure rather than
+    // throwing an exception (because web content can create huge runs).
+    void *storage = moz_malloc(aSize + aLength * sizeof(CompressedGlyph));
     if (!storage) {
-        NS_WARNING("failed to allocate glyph/text storage for text run!");
+        NS_WARNING("failed to allocate storage for text run!");
         return nsnull;
     }
 
-    // copy the text if we need to keep a copy in the textrun
-    if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) {
-        if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
-            PRUint8 *newText = reinterpret_cast<PRUint8*>(storage + aLength);
-            memcpy(newText, aText, aLength);
-            aText = newText;
-        } else {
-            PRUnichar *newText = reinterpret_cast<PRUnichar*>(storage + aLength);
-            memcpy(newText, aText, aLength*sizeof(PRUnichar));
-            aText = newText;
-        }
-    }
+    // Initialize the glyph storage (beyond aSize) to zero
+    memset(reinterpret_cast<char*>(storage) + aSize, 0,
+           aLength * sizeof(CompressedGlyph));
 
     return storage;
 }
 
 gfxTextRun *
 gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, const void *aText,
                    PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags)
 {
-    CompressedGlyph *glyphStorage = AllocateStorage(aText, aLength, aFlags);
-    if (!glyphStorage) {
+    void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
+    if (!storage) {
         return nsnull;
     }
 
-    return new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags, glyphStorage);
+    return new (storage) gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
 }
 
 gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText,
-                       PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags,
-                       CompressedGlyph *aGlyphStorage)
-  : mCharacterGlyphs(aGlyphStorage),
-    mUserData(aParams->mUserData),
+                       PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags)
+  : mUserData(aParams->mUserData),
     mFontGroup(aFontGroup),
     mAppUnitsPerDevUnit(aParams->mAppUnitsPerDevUnit),
-    mFlags(aFlags), mCharacterCount(aLength), mHashCode(0)
+    mFlags(aFlags), mCharacterCount(aLength)
 {
     NS_ASSERTION(mAppUnitsPerDevUnit != 0, "Invalid app unit scale");
     MOZ_COUNT_CTOR(gfxTextRun);
     NS_ADDREF(mFontGroup);
+
+    mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
+
     if (aParams->mSkipChars) {
         mSkipChars.TakeFrom(aParams->mSkipChars);
     }
 
-    if (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
-        mText.mSingle = static_cast<const PRUint8 *>(aText);
-    } else {
-        mText.mDouble = static_cast<const PRUnichar *>(aText);
-    }
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     AccountStorageForTextRun(this, 1);
 #endif
 
-    mUserFontSetGeneration = mFontGroup->GetGeneration();
     mSkipDrawing = mFontGroup->ShouldSkipDrawing();
 }
 
 gfxTextRun::~gfxTextRun()
 {
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     AccountStorageForTextRun(this, -1);
 #endif
 #ifdef DEBUG
     // Make it easy to detect a dead text run
     mFlags = 0xFFFFFFFF;
 #endif
 
-    // this will also delete the text, if it is owned by the run,
-    // because we merge the storage allocations
-    delete [] mCharacterGlyphs;
-
     NS_RELEASE(mFontGroup);
     MOZ_COUNT_DTOR(gfxTextRun);
 }
 
 bool
 gfxTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
                                    PRUint8 *aBreakBefore,
                                    gfxContext *aRefContext)
 {
     NS_ASSERTION(aStart + aLength <= mCharacterCount, "Overflow");
 
-    if (!mCharacterGlyphs)
-        return true;
     PRUint32 changed = 0;
     PRUint32 i;
+    CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart;
     for (i = 0; i < aLength; ++i) {
         PRUint8 canBreak = aBreakBefore[i];
-        if (canBreak && !mCharacterGlyphs[aStart + i].IsClusterStart()) {
+        if (canBreak && !charGlyphs[i].IsClusterStart()) {
             // This can happen ... there is no guarantee that our linebreaking rules
             // align with the platform's idea of what constitutes a cluster.
             NS_WARNING("Break suggested inside cluster!");
             canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
         }
-        changed |= mCharacterGlyphs[aStart + i].SetCanBreakBefore(canBreak);
+        changed |= charGlyphs[i].SetCanBreakBefore(canBreak);
     }
     return changed != 0;
 }
 
 gfxTextRun::LigatureData
 gfxTextRun::ComputeLigatureData(PRUint32 aPartStart, PRUint32 aPartEnd,
                                 PropertyProvider *aProvider)
 {
@@ -3798,71 +4322,16 @@ struct BufferAlphaColor {
         mContext->Restore();
     }
 
     gfxContext *mContext;
     gfxFloat mAlpha;
 };
 
 void
-gfxTextRun::AdjustAdvancesForSyntheticBold(gfxContext *aContext,
-                                           PRUint32 aStart,
-                                           PRUint32 aLength)
-{
-    const PRUint32 appUnitsPerDevUnit = GetAppUnitsPerDevUnit();
-    bool isRTL = IsRightToLeft();
-
-    GlyphRunIterator iter(this, aStart, aLength);
-    while (iter.NextRun()) {
-        gfxFont *font = iter.GetGlyphRun()->mFont;
-        if (font->IsSyntheticBold()) {
-            PRUint32 synAppUnitOffset =
-                font->GetSyntheticBoldOffset() *
-                    appUnitsPerDevUnit * CalcXScale(aContext);
-            PRUint32 start = iter.GetStringStart();
-            PRUint32 end = iter.GetStringEnd();
-            PRUint32 i;
-            
-            // iterate over glyphs, start to end
-            for (i = start; i < end; ++i) {
-                gfxTextRun::CompressedGlyph *glyphData = &mCharacterGlyphs[i];
-                
-                if (glyphData->IsSimpleGlyph()) {
-                    // simple glyphs ==> just add the advance
-                    PRInt32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
-                    if (CompressedGlyph::IsSimpleAdvance(advance)) {
-                        glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
-                    } else {
-                        // rare case, tested by making this the default
-                        PRUint32 glyphIndex = glyphData->GetSimpleGlyph();
-                        glyphData->SetComplex(true, true, 1);
-                        DetailedGlyph detail = {glyphIndex, advance, 0, 0};
-                        SetGlyphs(i, *glyphData, &detail);
-                    }
-                } else {
-                    // complex glyphs ==> add offset at cluster/ligature boundaries
-                    PRUint32 detailedLength = glyphData->GetGlyphCount();
-                    if (detailedLength) {
-                        gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(i);
-                        if (!details) {
-                            continue;
-                        }
-                        if (isRTL) {
-                            details[0].mAdvance += synAppUnitOffset;
-                        } else {
-                            details[detailedLength - 1].mAdvance += synAppUnitOffset;
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-void
 gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt,
                  PRUint32 aStart, PRUint32 aLength,
                  PropertyProvider *aProvider, gfxFloat *aAdvanceWidth)
 {
     NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range");
 
     gfxFloat direction = GetDirection();
 
@@ -4160,17 +4629,17 @@ gfxTextRun::BreakAndMeasureText(PRUint32
                 charAdvance += space->mBefore + space->mAfter;
             }
         } else {
             charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider);
         }
         
         advance += charAdvance;
         if (aTrimWhitespace) {
-            if (GetChar(i) == ' ') {
+            if (mCharacterGlyphs[i].CharIsSpace()) {
                 ++trimmableChars;
                 trimmableAdvance += charAdvance;
             } else {
                 trimmableAdvance = 0;
                 trimmableChars = 0;
             }
         }
     }
@@ -4284,16 +4753,20 @@ gfxTextRun::FindFirstGlyphRunContaining(
                  "Hmm, something went wrong, aOffset should have been found");
     return start;
 }
 
 nsresult
 gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType,
                         PRUint32 aUTF16Offset, bool aForceNewRun)
 {
+    NS_ASSERTION(aFont, "adding glyph run for null font!");
+    if (!aFont) {
+        return NS_OK;
+    }    
     PRUint32 numGlyphRuns = mGlyphRuns.Length();
     if (!aForceNewRun && numGlyphRuns > 0) {
         GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1];
 
         NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
                      "Glyph runs out of order (and run not forced)");
 
         // Don't append a run if the font is already the one we want
@@ -4373,19 +4846,20 @@ gfxTextRun::SanitizeGlyphRuns()
     if (mGlyphRuns.Length() <= 1)
         return;
 
     // If any glyph run starts with ligature-continuation characters, we need to advance it
     // to the first "real" character to avoid drawing partial ligature glyphs from wrong font
     // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes
     // it appear as if a ligature has been formed)
     PRInt32 i, lastRunIndex = mGlyphRuns.Length() - 1;
+    const CompressedGlyph *charGlyphs = mCharacterGlyphs;
     for (i = lastRunIndex; i >= 0; --i) {
         GlyphRun& run = mGlyphRuns[i];
-        while (mCharacterGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
+        while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
                run.mCharacterOffset < mCharacterCount) {
             run.mCharacterOffset++;
         }
         // if the run has become empty, eliminate it
         if ((i < lastRunIndex &&
              run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) ||
             (i == lastRunIndex && run.mCharacterOffset == mCharacterCount)) {
             mGlyphRuns.RemoveElementAt(i);
@@ -4407,20 +4881,16 @@ gfxTextRun::CountMissingGlyphs()
     return count;
 }
 
 gfxTextRun::DetailedGlyph *
 gfxTextRun::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount)
 {
     NS_ASSERTION(aIndex < mCharacterCount, "Index out of range");
 
-    if (!mCharacterGlyphs) {
-        return nsnull;
-    }
-
     if (!mDetailedGlyphs) {
         mDetailedGlyphs = new DetailedGlyphStore();
     }
 
     DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount);
     if (!details) {
         mCharacterGlyphs[aIndex].SetMissing(0);
         return nsnull;
@@ -4429,42 +4899,39 @@ gfxTextRun::AllocateDetailedGlyphs(PRUin
     return details;
 }
 
 void
 gfxTextRun::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph,
                       const DetailedGlyph *aGlyphs)
 {
     NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
-    NS_ASSERTION(aIndex > 0 ||
-                 (aGlyph.IsClusterStart() && aGlyph.IsLigatureGroupStart()),
-                 "First character must be the start of a cluster and can't be a ligature continuation!");
+    NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+                 "First character can't be a ligature continuation!");
 
     PRUint32 glyphCount = aGlyph.GetGlyphCount();
     if (glyphCount > 0) {
         DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
         if (!details)
             return;
         memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
     }
     mCharacterGlyphs[aIndex] = aGlyph;
 }
 
-#include "ignorable.x-ccmap"
-DEFINE_X_CCMAP(gIgnorableCCMapExt, const);
-
-static inline bool
-IsDefaultIgnorable(PRUint32 aChar)
-{
-    return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar);
-}
-
 void
 gfxTextRun::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar)
 {
+    PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aChar);
+    if (category >= HB_CATEGORY_COMBINING_MARK &&
+        category <= HB_CATEGORY_NON_SPACING_MARK)
+    {
+        mCharacterGlyphs[aIndex].SetComplex(false, true, 0);
+    }
+
     DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
     if (!details)
         return;
 
     details->mGlyphID = aChar;
     GlyphRun *glyphRun = &mGlyphRuns[FindFirstGlyphRunContaining(aIndex)];
     if (IsDefaultIgnorable(aChar)) {
         // Setting advance width to zero will prevent drawing the hexbox
@@ -4474,68 +4941,78 @@ gfxTextRun::SetMissingGlyph(PRUint32 aIn
                                 gfxFontMissingGlyphs::GetDesiredMinWidth(aChar));
         details->mAdvance = PRUint32(width*GetAppUnitsPerDevUnit());
     }
     details->mXOffset = 0;
     details->mYOffset = 0;
     mCharacterGlyphs[aIndex].SetMissing(1);
 }
 
-bool
-gfxTextRun::FilterIfIgnorable(PRUint32 aIndex)
+void
+gfxTextRun::CopyGlyphDataFrom(const gfxShapedWord *aShapedWord, PRUint32 aOffset)
 {
-    PRUint32 ch = GetChar(aIndex);
-    if (IsDefaultIgnorable(ch)) {
-        DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
-        if (details) {
-            details->mGlyphID = ch;
-            details->mAdvance = 0;
-            details->mXOffset = 0;
-            details->mYOffset = 0;
-            mCharacterGlyphs[aIndex].SetMissing(1);
-            return true;
+    PRUint32 wordLen = aShapedWord->Length();
+    NS_ASSERTION(aOffset + wordLen <= GetLength(),
+                 "word overruns end of textrun!");
+
+    const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs();
+    if (aShapedWord->HasDetailedGlyphs()) {
+        for (PRUint32 i = 0; i < wordLen; ++i, ++aOffset) {
+            const CompressedGlyph& g = wordGlyphs[i];
+            if (g.IsSimpleGlyph()) {
+                SetSimpleGlyph(aOffset, g);
+            } else {
+                const DetailedGlyph *details =
+                    g.GetGlyphCount() > 0 ?
+                        aShapedWord->GetDetailedGlyphs(i) : nsnull;
+                SetGlyphs(aOffset, g, details);
+            }
         }
-    }
-    return false;
+    } else {
+        memcpy(GetCharacterGlyphs() + aOffset, wordGlyphs,
+               wordLen * sizeof(CompressedGlyph));
+    }
 }
 
 void
 gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
                               PRUint32 aLength, PRUint32 aDest)
 {
     NS_ASSERTION(aStart + aLength <= aSource->GetLength(),
                  "Source substring out of range");
     NS_ASSERTION(aDest + aLength <= GetLength(),
                  "Destination substring out of range");
 
     if (aSource->mSkipDrawing) {
         mSkipDrawing = true;
     }
 
     // Copy base glyph data, and DetailedGlyph data where present
+    const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart;
+    CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest;
     for (PRUint32 i = 0; i < aLength; ++i) {
-        CompressedGlyph g = aSource->mCharacterGlyphs[i + aStart];
-        g.SetCanBreakBefore(mCharacterGlyphs[i + aDest].CanBreakBefore());
+        CompressedGlyph g = srcGlyphs[i];
+        g.SetCanBreakBefore(dstGlyphs[i].CanBreakBefore());
         if (!g.IsSimpleGlyph()) {
             PRUint32 count = g.GetGlyphCount();
             if (count > 0) {
                 DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count);
                 if (dst) {
                     DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart);
                     if (src) {
                         ::memcpy(dst, src, count * sizeof(DetailedGlyph));
                     } else {
                         g.SetMissing(0);
                     }
                 } else {
                     g.SetMissing(0);
                 }
             }
         }
-        mCharacterGlyphs[i + aDest] = g;
+        dstGlyphs[i] = g;
     }
 
     // Copy glyph runs
     GlyphRunIterator iter(aSource, aStart, aLength);
 #ifdef DEBUG
     gfxFont *lastFont = nsnull;
 #endif
     while (iter.NextRun()) {
@@ -4564,41 +5041,62 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun
         nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
                                   start - aStart + aDest, false);
         if (NS_FAILED(rv))
             return;
     }
 }
 
 void
-gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex)
+gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext,
+                          PRUint32 aCharIndex)
+{
+    if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) {
+        return;
+    }
+
+    aFont->InitWordCache();
+    static const PRUint8 space = ' ';
+    gfxShapedWord *sw = aFont->GetShapedWord(aContext,
+                                             &space, 1,
+                                             HashMix(0, ' '), 
+                                             HB_SCRIPT_LATIN,
+                                             mAppUnitsPerDevUnit,
+                                             gfxTextRunFactory::TEXT_IS_8BIT |
+                                             gfxTextRunFactory::TEXT_IS_ASCII |
+                                             gfxTextRunFactory::TEXT_IS_PERSISTENT);
+    if (sw) {
+        AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false);
+        CopyGlyphDataFrom(sw, aCharIndex);
+    }
+}
+
+bool
+gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext,
+                                  PRUint32 aCharIndex, PRUnichar aSpaceChar)
 {
     PRUint32 spaceGlyph = aFont->GetSpaceGlyph();
-    float spaceWidth = aFont->GetMetrics().spaceWidth;
-    PRUint32 spaceWidthAppUnits = NS_lroundf(spaceWidth*mAppUnitsPerDevUnit);
-    if (!spaceGlyph ||
-        !CompressedGlyph::IsSimpleGlyphID(spaceGlyph) ||
-        !CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
-        gfxTextRunFactory::Parameters params = {
-            aContext, nsnull, nsnull, nsnull, 0, mAppUnitsPerDevUnit
-        };
-        static const PRUint8 space = ' ';
-        nsAutoPtr<gfxTextRun> textRun;
-        textRun = mFontGroup->MakeTextRun(&space, 1, &params,
-            gfxTextRunFactory::TEXT_IS_8BIT | gfxTextRunFactory::TEXT_IS_ASCII |
-            gfxTextRunFactory::TEXT_IS_PERSISTENT);
-        if (!textRun || !textRun->mCharacterGlyphs)
-            return;
-        CopyGlyphDataFrom(textRun, 0, 1, aCharIndex);
-        return;
-    }
+    if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
+        return false;
+    }
+
+    PRUint32 spaceWidthAppUnits =
+        NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit);
+    if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
+        return false;
+    }
+
     AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false);
     CompressedGlyph g;
     g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
+    if (aSpaceChar == ' ') {
+        g.SetIsSpace();
+    }
     SetSimpleGlyph(aCharIndex, g);
+    return true;
 }
 
 void
 gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext)
 {
     bool needsGlyphExtents = NeedsGlyphExtents(this);
     if (!needsGlyphExtents && !mDetailedGlyphs)
         return;
@@ -4711,56 +5209,44 @@ gfxTextRun::ClusterIterator::ClusterAdva
     return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider);
 }
 
 size_t
 gfxTextRun::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf)
 {
     // The second arg is how much gfxTextRun::AllocateStorage would have
     // allocated.
-    size_t total =
-        aMallocSizeOf(mCharacterGlyphs,
-                      sizeof(CompressedGlyph) *
-                      GlyphStorageAllocCount(mCharacterCount, mFlags));
+    size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf);
 
     if (mDetailedGlyphs) {
         total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
     }
 
-    total += mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf);
-
     return total;
 }
 
 size_t
 gfxTextRun::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf)
 {
-    return aMallocSizeOf(this, sizeof(gfxTextRun)) +
+    // The second arg is how much gfxTextRun::AllocateStorageForTextRun would
+    // have allocated, given the character count of this run.
+    return aMallocSizeOf(this, sizeof(gfxTextRun) + sizeof(CompressedGlyph) * GetLength()) +
            SizeOfExcludingThis(aMallocSizeOf);
 }
 
 
 #ifdef DEBUG
 void
 gfxTextRun::Dump(FILE* aOutput) {
     if (!aOutput) {
         aOutput = stdout;
     }
 
     PRUint32 i;
-    fputc('"', aOutput);
-    for (i = 0; i < mCharacterCount; ++i) {
-        PRUnichar ch = GetChar(i);
-        if (ch >= 32 && ch < 128) {
-            fputc(ch, aOutput);
-        } else {
-            fprintf(aOutput, "\\u%4x", ch);
-        }
-    }
-    fputs("\" [", aOutput);
+    fputc('[', aOutput);
     for (i = 0; i < mGlyphRuns.Length(); ++i) {
         if (i > 0) {
             fputc(',', aOutput);
         }
         gfxFont* font = mGlyphRuns[i].mFont;
         const gfxFontStyle* style = font->GetStyle();
         NS_ConvertUTF16toUTF8 fontName(font->GetName());
         nsCAutoString lang;
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -66,16 +66,17 @@ typedef struct _cairo_scaled_font cairo_
 
 class gfxContext;
 class gfxTextRun;
 class gfxFont;
 class gfxFontFamily;
 class gfxFontGroup;
 class gfxUserFontSet;
 class gfxUserFontData;
+class gfxShapedWord;
 
 class nsILanguageAtomService;
 
 typedef struct _hb_blob_t hb_blob_t;
 
 // We should eliminate these synonyms when it won't cause many merge conflicts.
 #define FONT_STYLE_NORMAL              NS_FONT_STYLE_NORMAL
 #define FONT_STYLE_ITALIC              NS_FONT_STYLE_ITALIC
@@ -665,49 +666,54 @@ struct gfxTextRange {
  * When a font's refcount decreases to zero, instead of deleting it we
  * add it to our expiration tracker.
  * The expiration tracker tracks fonts with zero refcount. After a certain
  * period of time, such fonts expire and are deleted.
  *
  * We're using 3 generations with a ten-second generation interval, so
  * zero-refcount fonts will be deleted 20-30 seconds after their refcount
  * goes to zero, if timer events fire in a timely manner.
+ *
+ * The font cache also handles timed expiration of cached ShapedWords
+ * for "persistent" fonts: it has a repeating timer, and notifies
+ * each cached font to "age" its shaped words. The words will be released
+ * by the fonts if they get aged three times without being re-used in the
+ * meantime.
+ *
+ * Note that the ShapedWord timeout is much larger than the font timeout,
+ * so that in the case of a short-lived font, we'll discard the gfxFont
+ * completely, with all its words, and avoid the cost of aging the words
+ * individually. That only happens with longer-lived fonts.
  */
 class THEBES_API gfxFontCache : public nsExpirationTracker<gfxFont,3> {
 public:
-    enum { TIMEOUT_SECONDS = 10 };
-    gfxFontCache()
-        : nsExpirationTracker<gfxFont,3>(TIMEOUT_SECONDS*1000) { mFonts.Init(); }
-    ~gfxFontCache() {
-        // Expire everything that has a zero refcount, so we don't leak them.
-        AgeAllGenerations();
-        // All fonts should be gone.
-        NS_WARN_IF_FALSE(mFonts.Count() == 0,
-                         "Fonts still alive while shutting down gfxFontCache");
-        // Note that we have to delete everything through the expiration
-        // tracker, since there might be fonts not in the hashtable but in
-        // the tracker.
-    }
+    enum {
+        FONT_TIMEOUT_SECONDS = 10,
+        SHAPED_WORD_TIMEOUT_SECONDS = 60
+    };
+
+    gfxFontCache();
+    ~gfxFontCache();
 
     /*
      * Get the global gfxFontCache.  You must call Init() before
      * calling this method --- the result will not be null.
      */
     static gfxFontCache* GetCache() {
         return gGlobalCache;
     }
 
     static nsresult Init();
     // It's OK to call this even if Init() has not been called.
     static void Shutdown();
 
     // Look up a font in the cache. Returns an addrefed pointer, or null
     // if there's nothing matching in the cache
     already_AddRefed<gfxFont> Lookup(const gfxFontEntry *aFontEntry,
-                                     const gfxFontStyle *aFontGroup);
+                                     const gfxFontStyle *aStyle);
     // We created a new font (presumably because Lookup returned null);
     // put it in the cache. The font's refcount should be nonzero. It is
     // allowable to add a new font even if there is one already in the
     // cache with the same key; we'll forget about the old one.
     void AddNew(gfxFont *aFont);
 
     // The font's refcount has gone to zero; give ownership of it to
     // the cache. We delete it if it's not acquired again after a certain
@@ -755,16 +761,129 @@ protected:
             return NS_PTR_TO_INT32(aKey->mFontEntry) ^ aKey->mStyle->Hash();
         }
         enum { ALLOW_MEMMOVE = true };
 
         gfxFont* mFont;
     };
 
     nsTHashtable<HashEntry> mFonts;
+
+    static PLDHashOperator AgeCachedWordsForFont(HashEntry* aHashEntry, void*);
+    static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
+    nsCOMPtr<nsITimer>      mWordCacheExpirationTimer;
+};
+
+class THEBES_API gfxTextRunFactory {
+    NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
+
+public:
+    // Flags in the mask 0xFFFF0000 are reserved for textrun clients
+    // Flags in the mask 0x0000F000 are reserved for per-platform fonts
+    // Flags in the mask 0x00000FFF are set by the textrun creator.
+    enum {
+        CACHE_TEXT_FLAGS    = 0xF0000000,
+        USER_TEXT_FLAGS     = 0x0FFF0000,
+        PLATFORM_TEXT_FLAGS = 0x0000F000,
+        TEXTRUN_TEXT_FLAGS  = 0x00000FFF,
+        SETTABLE_FLAGS      = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
+
+        /**
+         * When set, the text string pointer used to create the text run
+         * is guaranteed to be available during the lifetime of the text run.
+         */
+        TEXT_IS_PERSISTENT           = 0x0001,
+        /**
+         * When set, the text is known to be all-ASCII (< 128).
+         */
+        TEXT_IS_ASCII                = 0x0002,
+        /**
+         * When set, the text is RTL.
+         */
+        TEXT_IS_RTL                  = 0x0004,
+        /**
+         * When set, spacing is enabled and the textrun needs to call GetSpacing
+         * on the spacing provider.
+         */
+        TEXT_ENABLE_SPACING          = 0x0008,
+        /**
+         * When set, GetHyphenationBreaks may return true for some character
+         * positions, otherwise it will always return false for all characters.
+         */
+        TEXT_ENABLE_HYPHEN_BREAKS    = 0x0010,
+        /**
+         * When set, the text has no characters above 255 and it is stored
+         * in the textrun in 8-bit format.
+         */
+        TEXT_IS_8BIT                 = 0x0020,
+        /**
+         * When set, the RunMetrics::mBoundingBox field will be initialized
+         * properly based on glyph extents, in particular, glyph extents that
+         * overflow the standard font-box (the box defined by the ascent, descent
+         * and advance width of the glyph). When not set, it may just be the
+         * standard font-box even if glyphs overflow.
+         */
+        TEXT_NEED_BOUNDING_BOX       = 0x0040,
+        /**
+         * When set, optional ligatures are disabled. Ligatures that are
+         * required for legible text should still be enabled.
+         */
+        TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080,
+        /**
+         * When set, the textrun should favour speed of construction over
+         * quality. This may involve disabling ligatures and/or kerning or
+         * other effects.
+         */
+        TEXT_OPTIMIZE_SPEED          = 0x0100,
+        /**
+         * For internal use by the memory reporter when accounting for
+         * storage used by textruns.
+         * Because the reporter may visit each textrun multiple times while
+         * walking the frame trees and textrun cache, it needs to mark
+         * textruns that have been seen so as to avoid multiple-accounting.
+         */
+        TEXT_RUN_SIZE_ACCOUNTED      = 0x0200,
+
+        /**
+         * nsTextFrameThebes sets these, but they're defined here rather than
+         * in nsTextFrameUtils.h because ShapedWord creation/caching also needs
+         * to check the _INCOMING flag
+         */
+        TEXT_TRAILING_ARABICCHAR = 0x20000000,
+        /**
+         * When set, the previous character for this textrun was an Arabic
+         * character.  This is used for the context detection necessary for
+         * bidi.numeral implementation.
+         */
+        TEXT_INCOMING_ARABICCHAR = 0x40000000,
+
+        TEXT_UNUSED_FLAGS = 0x90000000
+    };
+
+    /**
+     * This record contains all the parameters needed to initialize a textrun.
+     */
+    struct Parameters {
+        // A reference context suggesting where the textrun will be rendered
+        gfxContext   *mContext;
+        // Pointer to arbitrary user data (which should outlive the textrun)
+        void         *mUserData;
+        // A description of which characters have been stripped from the original
+        // DOM string to produce the characters in the textrun. May be null
+        // if that information is not relevant.
+        gfxSkipChars *mSkipChars;
+        // A list of where linebreaks are currently placed in the textrun. May
+        // be null if mInitialBreakCount is zero.
+        PRUint32     *mInitialBreaks;
+        PRUint32      mInitialBreakCount;
+        // The ratio to use to convert device pixels to application layout units
+        PRUint32      mAppUnitsPerDevUnit;
+    };
+
+    virtual ~gfxTextRunFactory() {}
 };
 
 /**
  * This stores glyph bounds information for a particular gfxFont, at
  * a particular appunits-per-dev-pixel ratio (because the compressed glyph
  * width array is stored in appunits).
  * 
  * We store a hashtable from glyph IDs to float bounding rects. For the
@@ -902,22 +1021,19 @@ public:
     gfxFontShaper(gfxFont *aFont)
         : mFont(aFont)
     {
         NS_ASSERTION(aFont, "shaper requires a valid font!");
     }
 
     virtual ~gfxFontShaper() { }
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript) = 0;
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText) = 0;
 
     gfxFont *GetFont() const { return mFont; }
 
 protected:
     // the font this shaper is working with
     gfxFont * mFont;
 };
 
@@ -1247,26 +1363,140 @@ public:
             return 0;
         }
         return mFontEntry->GetUVSGlyph(aCh, aVS); 
     }
 
     // call the (virtual) InitTextRun method to do glyph generation/shaping,
     // limiting the length of text passed by processing the run in multiple
     // segments if necessary
+    template<typename T>
     bool SplitAndInitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+                             gfxTextRun *aTextRun,
+                             const T *aString,
+                             PRUint32 aRunStart,
+                             PRUint32 aRunLength,
+                             PRInt32 aRunScript);
+
+    // Get a ShapedWord representing the given text (either 8- or 16-bit)
+    // for use in setting up a gfxTextRun.
+    template<typename T>
+    gfxShapedWord* GetShapedWord(gfxContext *aContext,
+                                 const T *aText,
+                                 PRUint32 aLength,
+                                 PRUint32 aHash,
+                                 PRInt32 aRunScript,
+                                 PRInt32 aAppUnitsPerDevUnit,
+                                 PRUint32 aFlags);
+
+    // Ensure the ShapedWord cache is initialized. This MUST be called before
+    // any attempt to use GetShapedWord().
+    void InitWordCache() {
+        if (!mWordCache.IsInitialized()) {
+            mWordCache.Init();
+        }
+    }
+
+    // Called by the gfxFontCache timer to increment the age of all the words,
+    // so that they'll expire after a sufficient period of non-use
+    void AgeCachedWords() {
+        if (mWordCache.IsInitialized()) {
+            (void)mWordCache.EnumerateEntries(AgeCacheEntry, this);
+        }
+    }
 
 protected:
+    // Call the appropriate shaper to generate glyphs for aText and store
+    // them into aShapedWord.
+    // The length of the text is aShapedWord->Length().
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText,
+                           bool aPreferPlatformShaping = false);
+
     nsRefPtr<gfxFontEntry> mFontEntry;
 
+    struct CacheHashKey {
+        union {
+            const PRUint8   *mSingle;
+            const PRUnichar *mDouble;
+        }                mText;
+        PRUint32         mLength;
+        PRUint32         mFlags;
+        PRInt32          mScript;
+        PRInt32          mAppUnitsPerDevUnit;
+        PLDHashNumber    mHashKey;
+        bool             mTextIs8Bit;
+
+        CacheHashKey(const PRUint8 *aText, PRUint32 aLength,
+                     PRUint32 aStringHash,
+                     PRInt32 aScriptCode, PRInt32 aAppUnitsPerDevUnit,
+                     PRUint32 aFlags)
+            : mLength(aLength),
+              mFlags(aFlags),
+              mScript(aScriptCode),
+              mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+              mHashKey(aStringHash + aScriptCode +
+                  aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000),
+              mTextIs8Bit(true)
+        {
+            NS_ASSERTION(aFlags & gfxTextRunFactory::TEXT_IS_8BIT,
+                         "8-bit flag should have been set");
+            mText.mSingle = aText;
+        }
+
+        CacheHashKey(const PRUnichar *aText, PRUint32 aLength,
+                     PRUint32 aStringHash,
+                     PRInt32 aScriptCode, PRInt32 aAppUnitsPerDevUnit,
+                     PRUint32 aFlags)
+            : mLength(aLength),
+              mFlags(aFlags),
+              mScript(aScriptCode),
+              mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+              mHashKey(aStringHash + aScriptCode +
+                  aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000),
+              mTextIs8Bit(false)
+        {
+            // We can NOT assert that TEXT_IS_8BIT is false in aFlags here,
+            // because this might be an 8bit-only word from a 16-bit textrun,
+            // in which case the text we're passed is still in 16-bit form,
+            // and we'll have to use an 8-to-16bit comparison in KeyEquals.
+            mText.mDouble = aText;
+        }
+    };
+
+    class CacheHashEntry : public PLDHashEntryHdr {
+    public:
+        typedef const CacheHashKey &KeyType;
+        typedef const CacheHashKey *KeyTypePointer;
+
+        // When constructing a new entry in the hashtable, the caller of Put()
+        // will fill us in.
+        CacheHashEntry(KeyTypePointer aKey) { }
+        CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
+        ~CacheHashEntry() { }
+
+        bool KeyEquals(const KeyTypePointer aKey) const;
+
+        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+        static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+            return aKey->mHashKey;
+        }
+
+        enum { ALLOW_MEMMOVE = true };
+
+        nsAutoPtr<gfxShapedWord> mShapedWord;
+    };
+
+    nsTHashtable<CacheHashEntry> mWordCache;
+
+    static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData);
+    static const PRUint32  kShapedWordCacheMaxAge = 3;
+
     bool                       mIsValid;
 
     // use synthetic bolding for environments where this is not supported
     // by the platform
     bool                       mApplySyntheticBold;
 
     nsExpirationState          mExpirationState;
     gfxFontStyle               mStyle;
@@ -1309,125 +1539,619 @@ protected:
     // Helper to calculate various derived metrics from the results of
     // InitMetricsFromSfntTables or equivalent platform code
     void CalculateDerivedMetrics(Metrics& aMetrics);
 
     // some fonts have bad metrics, this method sanitize them.
     // if this font has bad underline offset, aIsBadUnderlineFont should be true.
     void SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont);
 
-    // Default simply calls m[Platform|HarfBuzz]Shaper->InitTextRun().
-    // Override if the font class wants to give special handling
-    // to shaper failure.
-    // Returns false if shaping failed (though currently we
-    // don't have any good way to handle that situation).
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    // Bug 674909. When synthetic bolding text by drawing twice, need to
+    // render using a pixel offset in device pixels, otherwise text
+    // doesn't appear bolded, it appears as if a bad text shadow exists
+    // when a non-identity transform exists.  Use an offset factor so that
+    // the second draw occurs at a constant offset in device pixels.
+    // This helper calculates the scale factor we need to apply to the
+    // synthetic-bold offset.
+    static double CalcXScale(gfxContext *aContext);
 };
 
 // proportion of ascent used for x-height, if unable to read value from font
 #define DEFAULT_XHEIGHT_FACTOR 0.56f
 
-class THEBES_API gfxTextRunFactory {
-    NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
-
+/*
+ * gfxShapedWord stores a list of zero or more glyphs for each character. For each
+ * glyph we store the glyph ID, the advance, and possibly an xoffset and yoffset.
+ * The idea is that a string is rendered by a loop that draws each glyph
+ * at its designated offset from the current point, then advances the current
+ * point by the glyph's advance in the direction of the textrun (LTR or RTL).
+ * Each glyph advance is always rounded to the nearest appunit; this ensures
+ * consistent results when dividing the text in a textrun into multiple text
+ * frames (frame boundaries are always aligned to appunits). We optimize
+ * for the case where a character has a single glyph and zero xoffset and yoffset,
+ * and the glyph ID and advance are in a reasonable range so we can pack all
+ * necessary data into 32 bits.
+ *
+ * This glyph data is copied into gfxTextRuns as needed from the cache of
+ * ShapedWords associated with each gfxFont instance.
+ *
+ * gfxTextRun methods that measure or draw substrings will associate all the
+ * glyphs in a cluster with the first character of the cluster; if that character
+ * is in the substring, the glyphs will be measured or drawn, otherwise they
+ * won't.
+ */
+class gfxShapedWord
+{
 public:
-    // Flags in the mask 0xFFFF0000 are reserved for textrun clients
-    // Flags in the mask 0x0000F000 are reserved for per-platform fonts
-    // Flags in the mask 0x00000FFF are set by the textrun creator.
-    enum {
-        CACHE_TEXT_FLAGS    = 0xF0000000,
-        USER_TEXT_FLAGS     = 0x0FFF0000,
-        PLATFORM_TEXT_FLAGS = 0x0000F000,
-        TEXTRUN_TEXT_FLAGS  = 0x00000FFF,
-        SETTABLE_FLAGS      = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
-
-        /**
-         * When set, the text string pointer used to create the text run
-         * is guaranteed to be available during the lifetime of the text run.
-         */
-        TEXT_IS_PERSISTENT           = 0x0001,
-        /**
-         * When set, the text is known to be all-ASCII (< 128).
-         */
-        TEXT_IS_ASCII                = 0x0002,
-        /**
-         * When set, the text is RTL.
-         */
-        TEXT_IS_RTL                  = 0x0004,
-        /**
-         * When set, spacing is enabled and the textrun needs to call GetSpacing
-         * on the spacing provider.
-         */
-        TEXT_ENABLE_SPACING          = 0x0008,
+    static const PRUint32 kMaxLength = 0x7fff;
+
+    // Create a ShapedWord that can hold glyphs for aLength characters,
+    // with mCharacterGlyphs sized appropriately.
+    //
+    // Returns null on allocation failure (does NOT use infallible alloc)
+    // so caller must check for success.
+    //
+    // This does NOT perform shaping, so the returned word contains no
+    // glyph data; the caller must call gfxFont::Shape() with appropriate
+    // parameters to set up the glyphs.
+    static gfxShapedWord* Create(const PRUint8 *aText, PRUint32 aLength,
+                                 PRInt32 aRunScript,
+                                 PRInt32 aAppUnitsPerDevUnit,
+                                 PRUint32 aFlags) {
+        NS_ASSERTION(aLength <= kMaxLength, "excessive length for gfxShapedWord!");
+
+        // Compute size needed including the mCharacterGlyphs array
+        // and a copy of the original text
+        PRUint32 size =
+            offsetof(gfxShapedWord, mCharacterGlyphs) +
+            aLength * (sizeof(CompressedGlyph) + sizeof(PRUint8));
+        void *storage = moz_malloc(size);
+        if (!storage) {
+            return nsnull;
+        }
+
+        // Construct in the pre-allocated storage, using placement new
+        return new (storage) gfxShapedWord(aText, aLength, aRunScript,
+                                           aAppUnitsPerDevUnit, aFlags);
+    }
+
+    static gfxShapedWord* Create(const PRUnichar *aText, PRUint32 aLength,
+                                 PRInt32 aRunScript,
+                                 PRInt32 aAppUnitsPerDevUnit,
+                                 PRUint32 aFlags) {
+        NS_ASSERTION(aLength <= kMaxLength, "excessive length for gfxShapedWord!");
+
+        // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set,
+        // then we convert the text to an 8-bit version and call the 8-bit
+        // Create function instead.
+        if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) {
+            nsCAutoString narrowText;
+            LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength),
+                                    narrowText);
+            return Create((const PRUint8*)(narrowText.BeginReading()),
+                          aLength, aRunScript, aAppUnitsPerDevUnit, aFlags);
+        }
+
+        PRUint32 size =
+            offsetof(gfxShapedWord, mCharacterGlyphs) +
+            aLength * (sizeof(CompressedGlyph) + sizeof(PRUnichar));
+        void *storage = moz_malloc(size);
+        if (!storage) {
+            return nsnull;
+        }
+
+        return new (storage) gfxShapedWord(aText, aLength, aRunScript,
+                                           aAppUnitsPerDevUnit, aFlags);
+    }
+
+    // Override operator delete to properly free the object that was
+    // allocated via moz_malloc.
+    void operator delete(void* p) {
+        moz_free(p);
+    }
+
+    /**
+     * This class records the information associated with a character in the
+     * input string. It's optimized for the case where there is one glyph
+     * representing that character alone.
+     * 
+     * A character can have zero or more associated glyphs. Each glyph
+     * has an advance width and an x and y offset.
+     * A character may be the start of a cluster.
+     * A character may be the start of a ligature group.
+     * A character can be "missing", indicating that the system is unable
+     * to render the character.
+     * 
+     * All characters in a ligature group conceptually share all the glyphs
+     * associated with the characters in a group.
+     */
+    class CompressedGlyph {
+    public:
+        CompressedGlyph() { mValue = 0; }
+
+        enum {
+            // Indicates that a cluster and ligature group starts at this
+            // character; this character has a single glyph with a reasonable
+            // advance and zero offsets. A "reasonable" advance
+            // is one that fits in the available bits (currently 12) (specified
+            // in appunits).
+            FLAG_IS_SIMPLE_GLYPH  = 0x80000000U,
+
+            // Indicates whether a linebreak is allowed before this character;
+            // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value
+            // indicating the kind of linebreak (if any) allowed here.
+            FLAGS_CAN_BREAK_BEFORE = 0x60000000U,
+
+            FLAGS_CAN_BREAK_SHIFT = 29,
+            FLAG_BREAK_TYPE_NONE   = 0,
+            FLAG_BREAK_TYPE_NORMAL = 1,
+            FLAG_BREAK_TYPE_HYPHEN = 2,
+
+            FLAG_CHAR_IS_SPACE     = 0x10000000U,
+
+            // The advance is stored in appunits
+            ADVANCE_MASK  = 0x0FFF0000U,
+            ADVANCE_SHIFT = 16,
+
+            GLYPH_MASK = 0x0000FFFFU,
+
+            // Non-simple glyphs may or may not have glyph data in the
+            // corresponding mDetailedGlyphs entry. They have the following
+            // flag bits:
+
+            // When NOT set, indicates that this character corresponds to a
+            // missing glyph and should be skipped (or possibly, render the character
+            // Unicode value in some special way). If there are glyphs,
+            // the mGlyphID is actually the UTF16 character code. The bit is
+            // inverted so we can memset the array to zero to indicate all missing.
+            FLAG_NOT_MISSING              = 0x01,
+            FLAG_NOT_CLUSTER_START        = 0x02,
+            FLAG_NOT_LIGATURE_GROUP_START = 0x04,
+
+            FLAG_CHAR_IS_TAB              = 0x08,
+            FLAG_CHAR_IS_NEWLINE          = 0x10,
+            FLAG_CHAR_IS_LOW_SURROGATE    = 0x20,
+            
+            GLYPH_COUNT_MASK = 0x00FFFF00U,
+            GLYPH_COUNT_SHIFT = 8
+        };
+
+        // "Simple glyphs" have a simple glyph ID, simple advance and their
+        // x and y offsets are zero. Also the glyph extents do not overflow
+        // the font-box defined by the font ascent, descent and glyph advance width.
+        // These case is optimized to avoid storing DetailedGlyphs.
+
+        // Returns true if the glyph ID aGlyph fits into the compressed representation
+        static bool IsSimpleGlyphID(PRUint32 aGlyph) {
+            return (aGlyph & GLYPH_MASK) == aGlyph;
+        }
+        // Returns true if the advance aAdvance fits into the compressed representation.
+        // aAdvance is in appunits.
+        static bool IsSimpleAdvance(PRUint32 aAdvance) {
+            return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
+        }
+
+        bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; }
+        PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; }
+        PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; }
+
+        bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; }
+        bool IsClusterStart() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START);
+        }
+        bool IsLigatureGroupStart() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
+        }
+        bool IsLigatureContinuation() const {
+            return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 &&
+                (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) ==
+                    (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING);
+        }
+
+        // Return true if the original character was a normal (breakable,
+        // trimmable) space (U+0020). Not true for other characters that
+        // may happen to map to the space glyph (U+00A0).
+        bool CharIsSpace() const {
+            return (mValue & FLAG_CHAR_IS_SPACE) != 0;
+        }
+
+        bool CharIsTab() const {
+            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0;
+        }
+        bool CharIsNewline() const {
+            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0;
+        }
+        bool CharIsLowSurrogate() const {
+            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0;
+        }
+
+        void SetClusterStart(bool aIsClusterStart) {
+            NS_ASSERTION(!IsSimpleGlyph(),
+                         "can't call SetClusterStart on simple glyphs");
+            if (aIsClusterStart) {
+                mValue &= ~FLAG_NOT_CLUSTER_START;
+            } else {
+                mValue |= FLAG_NOT_CLUSTER_START;
+            }
+        }
+
+        PRUint8 CanBreakBefore() const {
+            return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT;
+        }
+        // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
+        PRUint32 SetCanBreakBefore(PRUint8 aCanBreakBefore) {
+            NS_ASSERTION(aCanBreakBefore <= 2,
+                         "Bogus break-before value!");
+            PRUint32 breakMask = (PRUint32(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT);
+            PRUint32 toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE);
+            mValue ^= toggle;
+            return toggle;
+        }
+
+        CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) {
+            NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow");
+            NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow");
+            mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) |
+                FLAG_IS_SIMPLE_GLYPH |
+                (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
+            return *this;
+        }
+        CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart,
+                PRUint32 aGlyphCount) {
+            mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) |
+                FLAG_NOT_MISSING |
+                (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) |
+                (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) |
+                (aGlyphCount << GLYPH_COUNT_SHIFT);
+            return *this;
+        }
         /**
-         * When set, GetHyphenationBreaks may return true for some character
-         * positions, otherwise it will always return false for all characters.
-         */
-        TEXT_ENABLE_HYPHEN_BREAKS    = 0x0010,
-        /**
-         * When set, the text has no characters above 255 and it is stored
-         * in the textrun in 8-bit format.
-         */
-        TEXT_IS_8BIT                 = 0x0020,
-        /**
-         * When set, the RunMetrics::mBoundingBox field will be initialized
-         * properly based on glyph extents, in particular, glyph extents that
-         * overflow the standard font-box (the box defined by the ascent, descent
-         * and advance width of the glyph). When not set, it may just be the
-         * standard font-box even if glyphs overflow.
+         * Missing glyphs are treated as ligature group starts; don't mess with
+         * the cluster-start flag (see bugs 618870 and 619286).
          */
-        TEXT_NEED_BOUNDING_BOX       = 0x0040,
-        /**
-         * When set, optional ligatures are disabled. Ligatures that are
-         * required for legible text should still be enabled.
-         */
-        TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080,
-        /**
-         * When set, the textrun should favour speed of construction over
-         * quality. This may involve disabling ligatures and/or kerning or
-         * other effects.
-         */
-        TEXT_OPTIMIZE_SPEED          = 0x0100,
-        /**
-         * For internal use by the memory reporter when accounting for
-         * storage used by textruns.
-         * Because the reporter may visit each textrun multiple times while
-         * walking the frame trees and textrun cache, it needs to mark
-         * textruns that have been seen so as to avoid multiple-accounting.
-         */
-        TEXT_RUN_SIZE_ACCOUNTED      = 0x0200
+        CompressedGlyph& SetMissing(PRUint32 aGlyphCount) {
+            mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START)) |
+                (aGlyphCount << GLYPH_COUNT_SHIFT);
+            return *this;
+        }
+        PRUint32 GetGlyphCount() const {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT;
+        }
+
+        void SetIsSpace() {
+            mValue |= FLAG_CHAR_IS_SPACE;
+        }
+        void SetIsTab() {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            mValue |= FLAG_CHAR_IS_TAB;
+        }
+        void SetIsNewline() {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            mValue |= FLAG_CHAR_IS_NEWLINE;
+        }
+        void SetIsLowSurrogate() {
+            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+            mValue |= FLAG_CHAR_IS_LOW_SURROGATE;
+        }
+
+    private:
+        PRUint32 mValue;
     };
 
     /**
-     * This record contains all the parameters needed to initialize a textrun.
+     * When the glyphs for a character don't fit into a CompressedGlyph record
+     * in SimpleGlyph format, we use an array of DetailedGlyphs instead.
      */
-    struct Parameters {
-        // A reference context suggesting where the textrun will be rendered
-        gfxContext   *mContext;
-        // Pointer to arbitrary user data (which should outlive the textrun)
-        void         *mUserData;
-        // A description of which characters have been stripped from the original
-        // DOM string to produce the characters in the textrun. May be null
-        // if that information is not relevant.
-        gfxSkipChars *mSkipChars;
-        // A list of where linebreaks are currently placed in the textrun. May
-        // be null if mInitialBreakCount is zero.
-        PRUint32     *mInitialBreaks;
-        PRUint32      mInitialBreakCount;
-        // The ratio to use to convert device pixels to application layout units
-        PRUint32      mAppUnitsPerDevUnit;
+    struct DetailedGlyph {
+        /** The glyphID, or the Unicode character
+         * if this is a missing glyph */
+        PRUint32 mGlyphID;
+        /** The advance, x-offset and y-offset of the glyph, in appunits
+         *  mAdvance is in the text direction (RTL or LTR)
+         *  mXOffset is always from left to right
+         *  mYOffset is always from top to bottom */   
+        PRInt32  mAdvance;
+        float    mXOffset, mYOffset;
     };
 
-    virtual ~gfxTextRunFactory() {}
+    bool IsClusterStart(PRUint32 aPos) {
+        NS_ASSERTION(aPos < Length(), "aPos out of range");
+        return mCharacterGlyphs[aPos].IsClusterStart();
+    }
+
+    bool IsLigatureGroupStart(PRUint32 aPos) {
+        NS_ASSERTION(aPos < Length(), "aPos out of range");
+        return mCharacterGlyphs[aPos].IsLigatureGroupStart();
+    }
+
+    PRUint32 Length() const {
+        return mLength;
+    }
+
+    const PRUint8* Text8Bit() const {
+        NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()");
+        return reinterpret_cast<const PRUint8*>(&mCharacterGlyphs[Length()]);
+    }
+
+    const PRUnichar* TextUnicode() const {
+        NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()");
+        return reinterpret_cast<const PRUnichar*>(&mCharacterGlyphs[Length()]);
+    }
+
+    PRUnichar GetCharAt(PRUint32 aOffset) const {
+        NS_ASSERTION(aOffset < Length(), "aOffset out of range");
+        return TextIs8Bit() ?
+            PRUnichar(Text8Bit()[aOffset]) : TextUnicode()[aOffset];
+    }
+
+    PRUint32 Flags() const {
+        return mFlags;
+    }
+
+    bool IsRightToLeft() const {
+        return (Flags() & gfxTextRunFactory::TEXT_IS_RTL) != 0;
+    }
+
+    float GetDirection() const {
+        return IsRightToLeft() ? -1.0 : 1.0;
+    }
+
+    bool DisableLigatures() const {
+        return (Flags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
+    }
+
+    bool TextIs8Bit() const {
+        return (Flags() & gfxTextRunFactory::TEXT_IS_8BIT) != 0;
+    }
+
+    PRInt32 Script() const {
+        return mScript;
+    }
+
+    PRInt32 AppUnitsPerDevUnit() const {
+        return mAppUnitsPerDevUnit;
+    }
+
+    void ResetAge() {
+        mAgeCounter = 0;
+    }
+    PRUint32 IncrementAge() {
+        return ++mAgeCounter;
+    }
+
+    void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
+        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
+        NS_ASSERTION(mCharacterGlyphs, "mCharacterGlyphs pointer is null!");
+        mCharacterGlyphs[aCharIndex] = aGlyph;
+    }
+
+    void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph,
+                   const DetailedGlyph *aGlyphs);
+
+    void SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar, gfxFont *aFont);
+
+    void SetIsSpace(PRUint32 aIndex) {
+        mCharacterGlyphs[aIndex].SetIsSpace();
+    }
+
+    void SetIsLowSurrogate(PRUint32 aIndex) {
+        SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nsnull);
+        mCharacterGlyphs[aIndex].SetIsLowSurrogate();
+    }
+
+    bool FilterIfIgnorable(PRUint32 aIndex);
+
+    const CompressedGlyph *GetCharacterGlyphs() const {
+        return &mCharacterGlyphs[0];
+    }
+
+    bool HasDetailedGlyphs() const {
+        return mDetailedGlyphs != nsnull;
+    }
+
+    // NOTE that this must not be called for a character offset that does
+    // not have any DetailedGlyph records; callers must have verified that
+    // mCharacterGlyphs[aCharIndex].GetGlyphCount() is greater than zero.
+    DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) const {
+        NS_ASSERTION(HasDetailedGlyphs() &&
+                     !mCharacterGlyphs[aCharIndex].IsSimpleGlyph() &&
+                     mCharacterGlyphs[aCharIndex].GetGlyphCount() > 0,
+                     "invalid use of GetDetailedGlyphs; check the caller!");
+        return mDetailedGlyphs->Get(aCharIndex);
+    }
+
+    void AdjustAdvancesForSyntheticBold(float aSynBoldOffset);
+
+    // this is a public static method in order to make it available
+    // for gfxTextRun to use directly on its own CompressedGlyph array,
+    // in addition to the use within ShapedWord
+    static void
+    SetupClusterBoundaries(CompressedGlyph *aGlyphs,
+                           const PRUnichar *aString, PRUint32 aLength);
+
+private:
+    // so that gfxTextRun can share our DetailedGlyphStore class
+    friend class gfxTextRun;
+
+    // Construct storage for a ShapedWord, ready to receive glyph data
+    gfxShapedWord(const PRUint8 *aText, PRUint32 aLength,
+                  PRInt32 aRunScript, PRInt32 aAppUnitsPerDevUnit,
+                  PRUint32 aFlags)
+        : mLength(aLength)
+        , mFlags(aFlags | gfxTextRunFactory::TEXT_IS_8BIT)
+        , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
+        , mScript(aRunScript)
+        , mAgeCounter(0)
+    {
+        memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
+        PRUint8 *text = reinterpret_cast<PRUint8*>(&mCharacterGlyphs[aLength]);
+        memcpy(text, aText, aLength * sizeof(PRUint8));
+    }
+
+    gfxShapedWord(const PRUnichar *aText, PRUint32 aLength,
+                  PRInt32 aRunScript, PRInt32 aAppUnitsPerDevUnit,
+                  PRUint32 aFlags)
+        : mLength(aLength)
+        , mFlags(aFlags)
+        , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
+        , mScript(aRunScript)
+        , mAgeCounter(0)
+    {
+        memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
+        PRUnichar *text = reinterpret_cast<PRUnichar*>(&mCharacterGlyphs[aLength]);
+        memcpy(text, aText, aLength * sizeof(PRUnichar));
+        SetupClusterBoundaries(&mCharacterGlyphs[0], aText, aLength);
+    }
+
+    // Allocate aCount DetailedGlyphs for the given index
+    DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex,
+                                          PRUint32 aCount);
+
+    // For characters whose glyph data does not fit the "simple" glyph criteria
+    // in CompressedGlyph, we use a sorted array to store the association
+    // between the source character offset and an index into an array 
+    // DetailedGlyphs. The CompressedGlyph record includes a count of
+    // the number of DetailedGlyph records that belong to the character,
+    // starting at the given index.
+    class DetailedGlyphStore {
+    public:
+        DetailedGlyphStore()
+            : mLastUsed(0)
+        { }
+
+        // This is optimized for the most common calling patterns:
+        // we rarely need random access to the records, access is most commonly
+        // sequential through the textRun, so we record the last-used index
+        // and check whether the caller wants the same record again, or the
+        // next; if not, it's most likely we're starting over from the start
+        // of the run, so we check the first entry before resorting to binary
+        // search as a last resort.
+        // NOTE that this must not be called for a character offset that does
+        // not have any DetailedGlyph records; callers must have verified that
+        // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero
+        // before calling this, otherwise the assertions here will fire (in a
+        // debug build), and we'll probably crash.
+        DetailedGlyph* Get(PRUint32 aOffset) {
+            NS_ASSERTION(mOffsetToIndex.Length() > 0,
+                         "no detailed glyph records!");
+            DetailedGlyph* details = mDetails.Elements();
+            // check common cases (fwd iteration, initial entry, etc) first
+            if (mLastUsed < mOffsetToIndex.Length() - 1 &&
+                aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) {
+                ++mLastUsed;
+            } else if (aOffset == mOffsetToIndex[0].mOffset) {
+                mLastUsed = 0;
+            } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) {
+                // do nothing
+            } else if (mLastUsed > 0 &&
+                       aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) {
+                --mLastUsed;
+            } else {
+                mLastUsed =
+                    mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset());
+            }
+            NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex,
+                         "detailed glyph record missing!");
+            return details + mOffsetToIndex[mLastUsed].mIndex;
+        }
+
+        DetailedGlyph* Allocate(PRUint32 aOffset, PRUint32 aCount) {
+            PRUint32 detailIndex = mDetails.Length();
+            DetailedGlyph *details = mDetails.AppendElements(aCount);
+            if (!details) {
+                return nsnull;
+            }
+            // We normally set up glyph records sequentially, so the common case
+            // here is to append new records to the mOffsetToIndex array;
+            // test for that before falling back to the InsertElementSorted
+            // method.
+            if (mOffsetToIndex.Length() == 0 ||
+                aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) {
+                if (!mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex))) {
+                    return nsnull;
+                }
+            } else {
+                if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex),
+                                                        CompareRecordOffsets())) {
+                    return nsnull;
+                }
+            }
+            return details;
+        }
+
+        size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) {
+            return aMallocSizeOf(this, sizeof(DetailedGlyphStore)) +
+                mDetails.SizeOfExcludingThis(aMallocSizeOf) +
+                mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf);
+        }
+
+    private:
+        struct DGRec {
+            DGRec(const PRUint32& aOffset, const PRUint32& aIndex)
+                : mOffset(aOffset), mIndex(aIndex) { }
+            PRUint32 mOffset; // source character offset in the textrun
+            PRUint32 mIndex;  // index where this char's DetailedGlyphs begin
+        };
+
+        struct CompareToOffset {
+            bool Equals(const DGRec& a, const PRUint32& b) const {
+                return a.mOffset == b;
+            }
+            bool LessThan(const DGRec& a, const PRUint32& b) const {
+                return a.mOffset < b;
+            }
+        };
+
+        struct CompareRecordOffsets {
+            bool Equals(const DGRec& a, const DGRec& b) const {
+                return a.mOffset == b.mOffset;
+            }
+            bool LessThan(const DGRec& a, const DGRec& b) const {
+                return a.mOffset < b.mOffset;
+            }
+        };
+
+        // Concatenated array of all the DetailedGlyph records needed for the
+        // textRun; individual character offsets are associated with indexes
+        // into this array via the mOffsetToIndex table.
+        nsTArray<DetailedGlyph>     mDetails;
+
+        // For each character offset that needs DetailedGlyphs, we record the
+        // index in mDetails where the list of glyphs begins. This array is
+        // sorted by mOffset.
+        nsTArray<DGRec>             mOffsetToIndex;
+
+        // Records the most recently used index into mOffsetToIndex, so that
+        // we can support sequential access more quickly than just doing
+        // a binary search each time.
+        nsTArray<DGRec>::index_type mLastUsed;
+    };
+
+    nsAutoPtr<DetailedGlyphStore>   mDetailedGlyphs;
+
+    // Number of PRUnichar characters and CompressedGlyph glyph records;
+    // note that gfx font code will never attempt to create a ShapedWord
+    // with a huge number of characters, so we could limit this to 16 bits
+    // to minimize memory usage for large numbers of cached words.
+    PRUint32                        mLength;
+
+    PRUint32                        mFlags;
+
+    PRInt32                         mAppUnitsPerDevUnit;
+    PRInt32                         mScript;
+
+    PRUint32                        mAgeCounter;
+
+    // The mCharacterGlyphs array is actually a variable-size member;
+    // when the ShapedWord is created, its size will be increased as necessary
+    // to allow the proper number of glyphs to be stored.
+    // The original text, in either 8-bit or 16-bit form, will be stored
+    // immediately following the CompressedGlyphs.
+    CompressedGlyph                 mCharacterGlyphs[1];
 };
 
 /**
  * gfxTextRun is an abstraction for drawing and measuring substrings of a run
  * of text. It stores runs of positioned glyph data, each run having a single
  * gfxFont. The glyphs are associated with a string of source text, and the
  * gfxTextRun APIs take parameters that are offsets into that source text.
  * 
@@ -1438,36 +2162,31 @@ public:
  * lazily by methods that need it, it is not cached. Line breaks are stored
  * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does
  * not actually do anything to explicitly account for line breaks). Initially
  * there are no line breaks. The textrun can record line breaks before or after
  * any given cluster. (Line breaks specified inside clusters are ignored.)
  * 
  * It is important that zero-length substrings are handled correctly. This will
  * be on the test!
- * 
- * gfxTextRun stores a list of zero or more glyphs for each character. For each
- * glyph we store the glyph ID, the advance, and possibly an xoffset and yoffset.
- * The idea is that a string is rendered by a loop that draws each glyph
- * at its designated offset from the current point, then advances the current
- * point by the glyph's advance in the direction of the textrun (LTR or RTL).
- * Each glyph advance is always rounded to the nearest appunit; this ensures
- * consistent results when dividing the text in a textrun into multiple text
- * frames (frame boundaries are always aligned to appunits). We optimize
- * for the case where a character has a single glyph and zero xoffset and yoffset,
- * and the glyph ID and advance are in a reasonable range so we can pack all
- * necessary data into 32 bits.
- * 
- * gfxTextRun methods that measure or draw substrings will associate all the
- * glyphs in a cluster with the first character of the cluster; if that character
- * is in the substring, the glyphs will be measured or drawn, otherwise they
- * won't.
  */
 class THEBES_API gfxTextRun {
 public:
+    // we use the same glyph storage as gfxShapedWord, to facilitate copying
+    // glyph data from shaped words into text runs as needed
+    typedef gfxShapedWord::CompressedGlyph    CompressedGlyph;
+    typedef gfxShapedWord::DetailedGlyph      DetailedGlyph;
+    typedef gfxShapedWord::DetailedGlyphStore DetailedGlyphStore;
+
+    // Override operator delete to properly free the object that was
+    // allocated via moz_malloc.
+    void operator delete(void* p) {
+        moz_free(p);
+    }
+
     virtual ~gfxTextRun();
 
     typedef gfxFont::RunMetrics Metrics;
 
     // Public textrun API for general use
 
     bool IsClusterStart(PRUint32 aPos) {
         NS_ASSERTION(aPos < mCharacterCount, "aPos out of range");
@@ -1483,16 +2202,33 @@ public:
             CompressedGlyph::FLAG_BREAK_TYPE_NORMAL;
     }
     bool CanHyphenateBefore(PRUint32 aPos) {
         NS_ASSERTION(aPos < mCharacterCount, "aPos out of range");
         return mCharacterGlyphs[aPos].CanBreakBefore() ==
             CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN;
     }
 
+    bool CharIsSpace(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsSpace();
+    }
+    bool CharIsTab(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsTab();
+    }
+    bool CharIsNewline(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsNewline();
+    }
+    bool CharIsLowSurrogate(PRUint32 aPos) {
+        NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range");
+        return mCharacterGlyphs[aPos].CharIsLowSurrogate();
+    }
+
     PRUint32 GetLength() { return mCharacterCount; }
 
     // All PRUint32 aStart, PRUint32 aLength ranges below are restricted to
     // grapheme cluster boundaries! All offsets are in terms of the string
     // passed into MakeTextRun.
     
     // All coordinates are in layout/app units
 
@@ -1758,191 +2494,23 @@ public:
     void ClearFlagBits(PRUint32 aFlags) {
       NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
                    "Only user flags should be mutable");
       mFlags &= ~aFlags;
     }
     const gfxSkipChars& GetSkipChars() const { return mSkipChars; }
     PRUint32 GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; }
     gfxFontGroup *GetFontGroup() const { return mFontGroup; }
-    const PRUint8 *GetText8Bit() const
-    { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle : nsnull; }
-    const PRUnichar *GetTextUnicode() const
-    { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? nsnull : mText.mDouble; }
-    const void *GetTextAt(PRUint32 aIndex) {
-        return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT)
-            ? static_cast<const void *>(mText.mSingle + aIndex)
-            : static_cast<const void *>(mText.mDouble + aIndex);
-    }
-    const PRUnichar GetChar(PRUint32 i) const
-    { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle[i] : mText.mDouble[i]; }
-    PRUint32 GetHashCode() const { return mHashCode; }
-    void SetHashCode(PRUint32 aHash) { mHashCode = aHash; }
+
 
     // Call this, don't call "new gfxTextRun" directly. This does custom
     // allocation and initialization
     static gfxTextRun *Create(const gfxTextRunFactory::Parameters *aParams,
         const void *aText, PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags);
 
-    /**
-     * This class records the information associated with a character in the
-     * input string. It's optimized for the case where there is one glyph
-     * representing that character alone.
-     * 
-     * A character can have zero or more associated glyphs. Each glyph
-     * has an advance width and an x and y offset.
-     * A character may be the start of a cluster.
-     * A character may be the start of a ligature group.
-     * A character can be "missing", indicating that the system is unable
-     * to render the character.
-     * 
-     * All characters in a ligature group conceptually share all the glyphs
-     * associated with the characters in a group.
-     */
-    class CompressedGlyph {
-    public:
-        CompressedGlyph() { mValue = 0; }
-
-        enum {
-            // Indicates that a cluster and ligature group starts at this
-            // character; this character has a single glyph with a reasonable
-            // advance and zero offsets. A "reasonable" advance
-            // is one that fits in the available bits (currently 13) (specified
-            // in appunits).
-            FLAG_IS_SIMPLE_GLYPH  = 0x80000000U,
-
-            // Indicates whether a linebreak is allowed before this character;
-            // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value
-            // indicating the kind of linebreak (if any) allowed here.
-            FLAGS_CAN_BREAK_BEFORE = 0x60000000U,
-
-            FLAGS_CAN_BREAK_SHIFT = 29,
-            FLAG_BREAK_TYPE_NONE   = 0,
-            FLAG_BREAK_TYPE_NORMAL = 1,
-            FLAG_BREAK_TYPE_HYPHEN = 2,
-
-            // The advance is stored in appunits
-            ADVANCE_MASK  = 0x1FFF0000U,
-            ADVANCE_SHIFT = 16,
-
-            GLYPH_MASK = 0x0000FFFFU,
-
-            // Non-simple glyphs may or may not have glyph data in the
-            // corresponding mDetailedGlyphs entry. They have the following
-            // flag bits:
-
-            // When NOT set, indicates that this character corresponds to a
-            // missing glyph and should be skipped (or possibly, render the character
-            // Unicode value in some special way). If there are glyphs,
-            // the mGlyphID is actually the UTF16 character code. The bit is
-            // inverted so we can memset the array to zero to indicate all missing.
-            FLAG_NOT_MISSING              = 0x01,
-            FLAG_NOT_CLUSTER_START        = 0x02,
-            FLAG_NOT_LIGATURE_GROUP_START = 0x04,
-            
-            GLYPH_COUNT_MASK = 0x00FFFF00U,
-            GLYPH_COUNT_SHIFT = 8
-        };
-
-        // "Simple glyphs" have a simple glyph ID, simple advance and their
-        // x and y offsets are zero. Also the glyph extents do not overflow
-        // the font-box defined by the font ascent, descent and glyph advance width.
-        // These case is optimized to avoid storing DetailedGlyphs.
-
-        // Returns true if the glyph ID aGlyph fits into the compressed representation
-        static bool IsSimpleGlyphID(PRUint32 aGlyph) {
-            return (aGlyph & GLYPH_MASK) == aGlyph;
-        }
-        // Returns true if the advance aAdvance fits into the compressed representation.
-        // aAdvance is in appunits.
-        static bool IsSimpleAdvance(PRUint32 aAdvance) {
-            return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
-        }
-
-        bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; }
-        PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; }
-        PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; }
-
-        bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; }
-        bool IsClusterStart() const {
-            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START);
-        }
-        bool IsLigatureGroupStart() const {
-            return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
-        }
-        bool IsLigatureContinuation() const {
-            return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 &&
-                (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) ==
-                    (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING);
-        }
-
-        PRUint8 CanBreakBefore() const {
-            return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT;
-        }
-        // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
-        PRUint32 SetCanBreakBefore(PRUint8 aCanBreakBefore) {
-            NS_ASSERTION(aCanBreakBefore <= 2,
-                         "Bogus break-before value!");
-            PRUint32 breakMask = (PRUint32(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT);
-            PRUint32 toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE);
-            mValue ^= toggle;
-            return toggle;
-        }
-
-        CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) {
-            NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow");
-            NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow");
-            mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) |
-                FLAG_IS_SIMPLE_GLYPH |
-                (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
-            return *this;
-        }
-        CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart,
-                PRUint32 aGlyphCount) {
-            mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) |
-                FLAG_NOT_MISSING |
-                (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) |
-                (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) |
-                (aGlyphCount << GLYPH_COUNT_SHIFT);
-            return *this;
-        }
-        /**
-         * Missing glyphs are treated as ligature group starts; don't mess with
-         * the cluster-start flag (see bugs 618870 and 619286).
-         */
-        CompressedGlyph& SetMissing(PRUint32 aGlyphCount) {
-            mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START)) |
-                (aGlyphCount << GLYPH_COUNT_SHIFT);
-            return *this;
-        }
-        PRUint32 GetGlyphCount() const {
-            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
-            return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT;
-        }
-
-    private:
-        PRUint32 mValue;
-    };
-
-    /**
-     * When the glyphs for a character don't fit into a CompressedGlyph record
-     * in SimpleGlyph format, we use an array of DetailedGlyphs instead.
-     */
-    struct DetailedGlyph {
-        /** The glyphID, or the Unicode character
-         * if this is a missing glyph */
-        PRUint32 mGlyphID;
-        /** The advance, x-offset and y-offset of the glyph, in appunits
-         *  mAdvance is in the text direction (RTL or LTR)
-         *  mXOffset is always from left to right
-         *  mYOffset is always from top to bottom */   
-        PRInt32  mAdvance;
-        float    mXOffset, mYOffset;
-    };
-
     // The text is divided into GlyphRuns as necessary
     struct GlyphRun {
         nsRefPtr<gfxFont> mFont;   // never null
         PRUint32          mCharacterOffset; // into original UTF16 string
         PRUint8           mMatchType;
     };
 
     class THEBES_API GlyphRunIterator {
@@ -2001,47 +2569,90 @@ public:
     nsresult AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType,
                          PRUint32 aStartCharIndex, bool aForceNewRun);
     void ResetGlyphRuns() { mGlyphRuns.Clear(); }
     void SortGlyphRuns();
     void SanitizeGlyphRuns();
 
     // Call the following glyph-setters during initialization or during reshaping
     // only. It is OK to overwrite existing data for a character.
+    void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
+        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
+        mCharacterGlyphs[aCharIndex] = aGlyph;
+    }
     /**
      * Set the glyph data for a character. aGlyphs may be null if aGlyph is a
      * simple glyph or has no associated glyphs. If non-null the data is copied,
      * the caller retains ownership.
      */
-    void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
-        NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
-        if (mCharacterGlyphs) {
-            mCharacterGlyphs[aCharIndex] = aGlyph;
-        }
-    }
     void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph,
                    const DetailedGlyph *aGlyphs);
     void SetMissingGlyph(PRUint32 aCharIndex, PRUint32 aUnicodeChar);
     void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex);
 
-    // If the character at aIndex is default-ignorable, set the glyph
-    // to be invisible-missing and return TRUE, else return FALSE
-    bool FilterIfIgnorable(PRUint32 aIndex);
+    // Set the glyph data for the given character index to the font's
+    // space glyph, IF this can be done as a "simple" glyph record
+    // (not requiring a DetailedGlyph entry). This avoids the need to call
+    // the font shaper and go through the shaped-word cache for most spaces.
+    //
+    // The parameter aSpaceChar is the original character code for which
+    // this space glyph is being used; if this is U+0020, we need to record
+    // that it could be trimmed at a run edge, whereas other kinds of space
+    // (currently just U+00A0) would not be trimmable/breakable.
+    //
+    // Returns true if it was able to set simple glyph data for the space;
+    // if it returns false, the caller needs to fall back to some other
+    // means to create the necessary (detailed) glyph data.
+    bool SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext,
+                               PRUint32 aCharIndex, PRUnichar aSpaceChar);
+
+    // Record the positions of specific characters that layout may need to
+    // detect in the textrun, even though it doesn't have an explicit copy
+    // of the original text. These are recorded using flag bits in the
+    // CompressedGlyph record; if necessary, we convert "simple" glyph records
+    // to "complex" ones as the Tab and Newline flags are not present in
+    // simple CompressedGlyph records.
+    void SetIsTab(PRUint32 aIndex) {
+        CompressedGlyph *g = &mCharacterGlyphs[aIndex];
+        if (g->IsSimpleGlyph()) {
+            DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+            details->mGlyphID = g->GetSimpleGlyph();
+            details->mAdvance = g->GetSimpleAdvance();
+            details->mXOffset = details->mYOffset = 0;
+            SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details);
+        }
+        g->SetIsTab();
+    }
+    void SetIsNewline(PRUint32 aIndex) {
+        CompressedGlyph *g = &mCharacterGlyphs[aIndex];
+        if (g->IsSimpleGlyph()) {
+            DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+            details->mGlyphID = g->GetSimpleGlyph();
+            details->mAdvance = g->GetSimpleAdvance();
+            details->mXOffset = details->mYOffset = 0;
+            SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details);
+        }
+        g->SetIsNewline();
+    }
+    void SetIsLowSurrogate(PRUint32 aIndex) {
+        SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nsnull);
+        mCharacterGlyphs[aIndex].SetIsLowSurrogate();
+    }
 
     /**
      * Prefetch all the glyph extents needed to ensure that Measure calls
      * on this textrun not requesting tight boundingBoxes will succeed. Note
      * that some glyph extents might not be fetched due to OOM or other
      * errors.
      */
     void FetchGlyphExtents(gfxContext *aRefContext);
 
     // API for access to the raw glyph data, needed by gfxFont::Draw
     // and gfxFont::GetBoundingBox
-    const CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; }
+    CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; }
 
     // NOTE that this must not be called for a character offset that does
     // not have any DetailedGlyph records; callers must have verified that
     // mCharacterGlyphs[aCharIndex].GetGlyphCount() is greater than zero.
     DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) {
         NS_ASSERTION(mDetailedGlyphs != nsnull &&
                      !mCharacterGlyphs[aCharIndex].IsSimpleGlyph() &&
                      mCharacterGlyphs[aCharIndex].GetGlyphCount() > 0,
@@ -2053,20 +2664,24 @@ public:
     PRUint32 CountMissingGlyphs();
     const GlyphRun *GetGlyphRuns(PRUint32 *aNumGlyphRuns) {
         *aNumGlyphRuns = mGlyphRuns.Length();
         return mGlyphRuns.Elements();
     }
     // Returns the index of the GlyphRun containing the given offset.
     // Returns mGlyphRuns.Length() when aOffset is mCharacterCount.
     PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset);
+
+    // Copy glyph data from a ShapedWord into this textrun.
+    void CopyGlyphDataFrom(const gfxShapedWord *aSource, PRUint32 aStart);
+
     // Copy glyph data for a range of characters from aSource to this
     // textrun.
-    virtual void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
-                                   PRUint32 aLength, PRUint32 aDest);
+    void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart,
+                           PRUint32 aLength, PRUint32 aDest);
 
     nsExpirationState *GetExpirationState() { return &mExpirationState; }
 
     struct LigatureData {
         // textrun offsets of the start and end of the containing ligature
         PRUint32 mLigatureStart;
         PRUint32 mLigatureEnd;
         // appunits advance to the start of the ligature part within the ligature;
@@ -2076,19 +2691,16 @@ public:
         // when the part is at the start of the ligature, and after-spacing
         // when the part is as the end of the ligature
         gfxFloat mPartWidth;
         
         bool mClipBeforePart;
         bool mClipAfterPart;
     };
     
-    // user font set generation when text run was created
-    PRUint64 GetUserFontSetGeneration() { return mUserFontSetGeneration; }
-
     // return storage used by this run, for memory reporter;
     // nsTransformedTextRun needs to override this as it holds additional data
     virtual NS_MUST_OVERRIDE size_t
         SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf);
     virtual NS_MUST_OVERRIDE size_t
         SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf);
 
     // Get the size, if it hasn't already been gotten, marking as it goes.
@@ -2099,49 +2711,41 @@ public:
         mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
         return SizeOfIncludingThis(aMallocSizeOf);
     }
     void ResetSizeOfAccountingFlags() {
         mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
     }
 
 #ifdef DEBUG
-    // number of entries referencing this textrun in the gfxTextRunWordCache
-    PRUint32 mCachedWords;
-    // generation of gfxTextRunWordCache that refers to this textrun;
-    // if the cache gets cleared, then mCachedWords is no longer meaningful
-    PRUint32 mCacheGeneration;
-    
     void Dump(FILE* aOutput);
 #endif
 
-    // post-process glyph advances to deal with synthetic bolding
-    void AdjustAdvancesForSyntheticBold(gfxContext *aContext,
-                                        PRUint32 aStart,
-                                        PRUint32 aLength);
-
 protected:
     /**
-     * Initializes the textrun to blank.
-     * @param aGlyphStorage preallocated array of CompressedGlyph[aLength]
-     * for the textrun to use; if aText is not persistent, then it has also
-     * been appended to this array, so it must NOT be freed separately.
+     * Create a textrun, and set its mCharacterGlyphs to point immediately
+     * after the base object; this is ONLY used in conjunction with placement
+     * new, after allocating a block large enough for the glyph records to
+     * follow the base textrun object.
      */
     gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText,
-               PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags,
-               CompressedGlyph *aGlyphStorage);
+               PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags);
 
     /**
      * Helper for the Create() factory method to allocate the required
-     * glyph storage, and copy the text (modifying the aText parameter)
-     * if it is not flagged as persistent.
+     * glyph storage for a textrun object with the basic size aSize,
+     * plus room for aLength glyph records.
      */
-    static CompressedGlyph* AllocateStorage(const void*& aText,
-                                            PRUint32 aLength,
-                                            PRUint32 aFlags);
+    static void* AllocateStorageForTextRun(size_t aSize, PRUint32 aLength);
+
+    // All our glyph data is in logical order, not visual.
+    // Space for mCharacterGlyphs is allocated fused with the textrun object,
+    // and then the constructor sets the pointer to the beginning of this
+    // storage area. Thus, this pointer must NOT be freed!
+    CompressedGlyph  *mCharacterGlyphs;
 
 private:
     // **** general helpers **** 
 
     // Allocate aCount DetailedGlyphs for the given index
     DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, PRUint32 aCount);
 
     // Get the total advance for a range of glyphs.
@@ -2189,166 +2793,29 @@ private:
                                  Metrics *aMetrics);
 
     // **** drawing helper ****
     void DrawGlyphs(gfxFont *aFont, gfxContext *aContext, bool aDrawToPath,
                     gfxPoint *aPt, PRUint32 aStart, PRUint32 aEnd,
                     PropertyProvider *aProvider,
                     PRUint32 aSpacingStart, PRUint32 aSpacingEnd);
 
-    // All our glyph data is in logical order, not visual.
-    // mCharacterGlyphs is allocated by the factory that creates the textrun,
-    // to avoid the possibility of failure during the constructor;
-    // however, ownership passes to the textrun during construction and so
-    // it must be deleted in the destructor.
-    CompressedGlyph*                               mCharacterGlyphs;
-
-    // For characters whose glyph data does not fit the "simple" glyph criteria
-    // in CompressedGlyph, we use a sorted array to store the association
-    // between the source character offset and an index into an array 
-    // DetailedGlyphs. The CompressedGlyph record includes a count of
-    // the number of DetailedGlyph records that belong to the character,
-    // starting at the given index.
-    class DetailedGlyphStore {
-    public:
-        DetailedGlyphStore()
-            : mLastUsed(0)
-        { }
-
-        // This is optimized for the most common calling patterns:
-        // we rarely need random access to the records, access is most commonly
-        // sequential through the textRun, so we record the last-used index
-        // and check whether the caller wants the same record again, or the
-        // next; if not, it's most likely we're starting over from the start
-        // of the run, so we check the first entry before resorting to binary
-        // search as a last resort.
-        // NOTE that this must not be called for a character offset that does
-        // not have any DetailedGlyph records; callers must have verified that
-        // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero
-        // before calling this, otherwise the assertions here will fire (in a
-        // debug build), and we'll probably crash.
-        DetailedGlyph* Get(PRUint32 aOffset) {
-            NS_ASSERTION(mOffsetToIndex.Length() > 0,
-                         "no detailed glyph records!");
-            DetailedGlyph* details = mDetails.Elements();
-            // check common cases (fwd iteration, initial entry, etc) first
-            if (mLastUsed < mOffsetToIndex.Length() - 1 &&
-                aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) {
-                ++mLastUsed;
-            } else if (aOffset == mOffsetToIndex[0].mOffset) {
-                mLastUsed = 0;
-            } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) {
-                // do nothing
-            } else if (mLastUsed > 0 &&
-                       aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) {
-                --mLastUsed;
-            } else {
-                mLastUsed =
-                    mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset());
-            }
-            NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex,
-                         "detailed glyph record missing!");
-            return details + mOffsetToIndex[mLastUsed].mIndex;
-        }
-
-        DetailedGlyph* Allocate(PRUint32 aOffset, PRUint32 aCount) {
-            PRUint32 detailIndex = mDetails.Length();
-            DetailedGlyph *details = mDetails.AppendElements(aCount);
-            if (!details) {
-                return nsnull;
-            }
-            // We normally set up glyph records sequentially, so the common case
-            // here is to append new records to the mOffsetToIndex array;
-            // test for that before falling back to the InsertElementSorted
-            // method.
-            if (mOffsetToIndex.Length() == 0 ||
-                aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) {
-                if (!mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex))) {
-                    return nsnull;
-                }
-            } else {
-                if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex),
-                                                        CompareRecordOffsets())) {
-                    return nsnull;
-                }
-            }
-            return details;
-        }
-
-        size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) {
-            return aMallocSizeOf(this, sizeof(DetailedGlyphStore)) +
-                mDetails.SizeOfExcludingThis(aMallocSizeOf) +
-                mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf);
-        }
-
-    private:
-        struct DGRec {
-            DGRec(const PRUint32& aOffset, const PRUint32& aIndex)
-                : mOffset(aOffset), mIndex(aIndex) { }
-            PRUint32 mOffset; // source character offset in the textrun
-            PRUint32 mIndex;  // index where this char's DetailedGlyphs begin
-        };
-
-        struct CompareToOffset {
-            bool Equals(const DGRec& a, const PRUint32& b) const {
-                return a.mOffset == b;
-            }
-            bool LessThan(const DGRec& a, const PRUint32& b) const {
-                return a.mOffset < b;
-            }
-        };
-
-        struct CompareRecordOffsets {
-            bool Equals(const DGRec& a, const DGRec& b) const {
-                return a.mOffset == b.mOffset;
-            }
-            bool LessThan(const DGRec& a, const DGRec& b) const {
-                return a.mOffset < b.mOffset;
-            }
-        };
-
-        // Concatenated array of all the DetailedGlyph records needed for the
-        // textRun; individual character offsets are associated with indexes
-        // into this array via the mOffsetToIndex table.
-        nsTArray<DetailedGlyph>     mDetails;
-
-        // For each character offset that needs DetailedGlyphs, we record the
-        // index in mDetails where the list of glyphs begins. This array is
-        // sorted by mOffset.
-        nsTArray<DGRec>             mOffsetToIndex;
-
-        // Records the most recently used index into mOffsetToIndex, so that
-        // we can support sequential access more quickly than just doing
-        // a binary search each time.
-        nsTArray<DGRec>::index_type mLastUsed;
-    };
-
     nsAutoPtr<DetailedGlyphStore>   mDetailedGlyphs;
 
     // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*,
     // for smaller size especially in the super-common one-glyphrun case
     nsAutoTArray<GlyphRun,1>        mGlyphRuns;
-    // When TEXT_IS_8BIT is set, we use mSingle, otherwise we use mDouble.
-    // When TEXT_IS_PERSISTENT is set, we don't own the text, otherwise we
-    // own the text. When we own the text, it's allocated fused with the
-    // mCharacterGlyphs array, and therefore need not be explicitly deleted.
-    // This text is not null-terminated.
-    union {
-        const PRUint8   *mSingle;
-        const PRUnichar *mDouble;
-    } mText;
+
     void             *mUserData;
     gfxFontGroup     *mFontGroup; // addrefed
     gfxSkipChars      mSkipChars;
     nsExpirationState mExpirationState;
     PRUint32          mAppUnitsPerDevUnit;
     PRUint32          mFlags;
     PRUint32          mCharacterCount;
-    PRUint32          mHashCode;
-    PRUint64          mUserFontSetGeneration; // user font set generation when text run created
 
     bool              mSkipDrawing; // true if the font group we used had a user font
                                     // download that's in progress, so we should hide text
                                     // until the download completes (or timeout fires)
 };
 
 class THEBES_API gfxFontGroup : public gfxTextRunFactory {
 public:
@@ -2380,31 +2847,21 @@ public:
             mStyle.Equals(other.mStyle);
     }
 
     const gfxFontStyle *GetStyle() const { return &mStyle; }
 
     virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle);
 
     /**
-     * The listed characters should not be passed in to MakeTextRun and should
-     * be treated as invisible and zero-width.
+     * The listed characters should be treated as invisible and zero-width
+     * when creating textruns.
      */
+    static bool IsInvalidChar(PRUint8 ch);
     static bool IsInvalidChar(PRUnichar ch);
-    
-    /**
-     * Make a textrun for an empty string. This is fast; if you call it,
-     * don't bother caching the result.
-     */
-    gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags);
-    /**
-     * Make a textrun for a single ASCII space. This is fast; if you call it,
-     * don't bother caching the result.
-     */
-    gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags);
 
     /**
      * Make a textrun for a given string.
      * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
      * textrun will copy it.
      * This calls FetchGlyphExtents on the textrun.
      */
     virtual gfxTextRun *MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
@@ -2413,16 +2870,32 @@ public:
      * Make a textrun for a given string.
      * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
      * textrun will copy it.
      * This calls FetchGlyphExtents on the textrun.
      */
     virtual gfxTextRun *MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
                                     const Parameters *aParams, PRUint32 aFlags);
 
+    /**
+     * Textrun creation helper for clients that don't want to pass
+     * a full Parameters record.
+     */
+    template<typename T>
+    gfxTextRun *MakeTextRun(const T *aString, PRUint32 aLength,
+                            gfxContext *aRefContext,
+                            PRUint32 aAppUnitsPerDevUnit,
+                            PRUint32 aFlags)
+    {
+        gfxTextRunFactory::Parameters params = {
+            aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
+        };
+        return MakeTextRun(aString, aLength, &params, aFlags);
+    }
+
     /* helper function for splitting font families on commas and
      * calling a function for each family to fill the mFonts array
      */
     typedef bool (*FontCreationCallback) (const nsAString& aName,
                                             const nsACString& aGenericName,
                                             bool aUseFontSet,
                                             void *closure);
     bool ForEachFont(const nsAString& aFamilies,
@@ -2456,18 +2929,19 @@ public:
                         gfxFont *aPrevMatchedFont,
                         PRUint8 *aMatchType);
 
     // search through pref fonts for a character, return nsnull if no matching pref font
     virtual already_AddRefed<gfxFont> WhichPrefFontSupportsChar(PRUint32 aCh);
 
     virtual already_AddRefed<gfxFont> WhichSystemFontSupportsChar(PRUint32 aCh);
 
+    template<typename T>
     void ComputeRanges(nsTArray<gfxTextRange>& mRanges,
-                       const PRUnichar *aString, PRUint32 begin, PRUint32 end,
+                       const T *aString, PRUint32 aLength,
                        PRInt32 aRunScript);
 
     gfxUserFontSet* GetUserFontSet();
 
     // With downloadable fonts, the composition of the font group can change as fonts are downloaded
     // for each change in state of the user font set, the generation value is bumped to avoid picking up
     // previously created text runs in the text run word cache.  For font groups based on stylesheets
     // with no @font-face rule, this always returns 0.
@@ -2496,41 +2970,51 @@ protected:
     eFontPrefLang           mLastPrefLang;       // lang group for last pref font
     eFontPrefLang           mPageLang;
     bool                    mLastPrefFirstFont;  // is this the first font in the list of pref fonts for this lang group?
 
     bool                    mSkipDrawing; // hide text while waiting for a font
                                           // download to complete (or fallback
                                           // timer to fire)
 
+    /**
+     * Textrun creation short-cuts for special cases where we don't need to
+     * call a font shaper to generate glyphs.
+     */
+    gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags);
+    gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags);
+    gfxTextRun *MakeBlankTextRun(const void* aText, PRUint32 aLength,
+                                 const Parameters *aParams, PRUint32 aFlags);
+
     // Used for construction/destruction.  Not intended to change the font set
     // as invalidation of font lists and caches is not considered.
     void SetUserFontSet(gfxUserFontSet *aUserFontSet);
 
     // Initialize the list of fonts
     void BuildFontList();
 
     // Init this font group's font metrics. If there no bad fonts, you don't need to call this.
     // But if there are one or more bad fonts which have bad underline offset,
     // you should call this with the *first* bad font.
     void InitMetricsForBadFont(gfxFont* aBadFont);
 
     // Set up the textrun glyphs for an entire text run:
     // find script runs, and then call InitScriptRun for each
+    template<typename T>
     void InitTextRun(gfxContext *aContext,
                      gfxTextRun *aTextRun,
-                     const PRUnichar *aString,
+                     const T *aString,
                      PRUint32 aLength);
 
     // InitTextRun helper to handle a single script run, by finding font ranges
     // and calling each font's InitTextRun() as appropriate
+    template<typename T>
     void InitScriptRun(gfxContext *aContext,
                        gfxTextRun *aTextRun,
-                       const PRUnichar *aString,
-                       PRUint32 aTotalLength,
+                       const T *aString,
                        PRUint32 aScriptRunStart,
                        PRUint32 aScriptRunEnd,
                        PRInt32 aRunScript);
 
     /* If aResolveGeneric is true, then CSS/Gecko generic family names are
      * replaced with preferred fonts.
      *
      * If aResolveFontName is true then fc() is called only for existing fonts
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -114,48 +114,43 @@ gfxGDIFont::CreatePlatformShaper()
 gfxFont*
 gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
 {
     return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()),
                           &mStyle, mNeedsBold, anAAOption);
 }
 
 static bool
-UseUniscribe(gfxTextRun *aTextRun,
-             const PRUnichar *aString,
-             PRUint32 aRunStart,
-             PRUint32 aRunLength)
+UseUniscribe(gfxShapedWord *aShapedWord,
+             const PRUnichar *aString)
 {
-    PRUint32 flags = aTextRun->GetFlags();
+    PRUint32 flags = aShapedWord->Flags();
     bool useGDI;
 
     bool isXP = (gfxWindowsPlatform::WindowsOSVersion() 
                        < gfxWindowsPlatform::kWindowsVista);
 
     // bug 561304 - Uniscribe bug produces bad positioning at certain
     // font sizes on XP, so default to GDI on XP using logic of 3.6
 
     useGDI = isXP &&
              (flags &
                (gfxTextRunFactory::TEXT_OPTIMIZE_SPEED | 
                 gfxTextRunFactory::TEXT_IS_RTL)
              ) == gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
 
     return !useGDI ||
-        ScriptIsComplex(aString + aRunStart, aRunLength, SIC_COMPLEX) == S_OK;
+        ScriptIsComplex(aString, aShapedWord->Length(), SIC_COMPLEX) == S_OK;
 }
 
 bool
-gfxGDIFont::InitTextRun(gfxContext *aContext,
-                        gfxTextRun *aTextRun,
-                        const PRUnichar *aString,
-                        PRUint32 aRunStart,
-                        PRUint32 aRunLength,
-                        PRInt32 aRunScript,
-                        bool aPreferPlatformShaping)
+gfxGDIFont::ShapeWord(gfxContext *aContext,
+                      gfxShapedWord *aShapedWord,
+                      const PRUnichar *aString,
+                      bool aPreferPlatformShaping)
 {
     if (!mMetrics) {
         Initialize();
     }
     if (!mIsValid) {
         NS_WARNING("invalid font! expect incorrect text rendering");
         return false;
     }
@@ -163,81 +158,66 @@ gfxGDIFont::InitTextRun(gfxContext *aCon
     bool ok = false;
 
     // ensure the cairo font is set up, so there's no risk it'll fall back to
     // creating a "toy" font internally (see bug 544617)
     SetupCairoFont(aContext);
 
 #ifdef MOZ_GRAPHITE
     if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
-        ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString,
-                                          aRunStart, aRunLength, 
-                                          aRunScript);
+        ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString);
     }
 #endif
 
     if (!ok && mHarfBuzzShaper) {
-        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) {
-            ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength, 
-                                              aRunScript);
+        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) {
+            ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString);
         }
     }
 
     if (!ok) {
         GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
         bool preferUniscribe =
             (!fe->IsTrueType() || fe->IsSymbolFont()) && !fe->mForceGDI;
 
-        if (preferUniscribe ||
-            UseUniscribe(aTextRun, aString, aRunStart, aRunLength))
-        {
+        if (preferUniscribe || UseUniscribe(aShapedWord, aString)) {
             // first try Uniscribe
             if (!mUniscribeShaper) {
                 mUniscribeShaper = new gfxUniscribeShaper(this);
             }
 
-            ok = mUniscribeShaper->InitTextRun(aContext, aTextRun, aString,
-                                               aRunStart, aRunLength, 
-                                               aRunScript);
+            ok = mUniscribeShaper->ShapeWord(aContext, aShapedWord, aString);
             if (ok) {
                 return true;
             }
 
             // fallback to GDI shaping
             if (!mPlatformShaper) {
                 CreatePlatformShaper();
             }
 
-            ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength, 
-                                              aRunScript);
+            ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aString);
         } else {
             // first use GDI
             if (!mPlatformShaper) {
                 CreatePlatformShaper();
             }
 
-            ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString,
-                                              aRunStart, aRunLength, 
-                                              aRunScript);
-
+            ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aString);
             if (ok) {
                 return true;
             }
 
             // try Uniscribe if GDI failed
             if (!mUniscribeShaper) {
                 mUniscribeShaper = new gfxUniscribeShaper(this);
             }
 
             // use Uniscribe shaping
-            ok = mUniscribeShaper->InitTextRun(aContext, aTextRun, aString,
-                                               aRunStart, aRunLength, 
-                                               aRunScript);
+            ok = mUniscribeShaper->ShapeWord(aContext, aShapedWord, aString);
         }
 
 #if DEBUG
         if (!ok) {
             NS_ConvertUTF16toUTF8 name(GetName());
             char msg[256];
 
             sprintf(msg, 
--- a/gfx/thebes/gfxGDIFont.h
+++ b/gfx/thebes/gfxGDIFont.h
@@ -87,23 +87,20 @@ public:
 
     // get hinted glyph width in pixels as 16.16 fixed-point value
     virtual PRInt32 GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID);
 
 protected:
     virtual void CreatePlatformShaper();
 
     /* override to check for uniscribe failure and fall back to GDI */
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript,
-                               bool aPreferPlatformShaping = false);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString,
+                           bool aPreferPlatformShaping = false);
 
     void Initialize(); // creates metrics and Cairo fonts
 
     void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize);
 
     // mPlatformShaper is used for the GDI shaper, mUniscribeShaper
     // for the Uniscribe version if needed
     nsAutoPtr<gfxFontShaper>   mUniscribeShaper;
--- a/gfx/thebes/gfxGDIShaper.cpp
+++ b/gfx/thebes/gfxGDIShaper.cpp
@@ -45,86 +45,84 @@
 
 /**********************************************************************
  *
  * class gfxGDIShaper
  *
  **********************************************************************/
 
 bool
-gfxGDIShaper::InitTextRun(gfxContext *aContext,
-                          gfxTextRun *aTextRun,
-                          const PRUnichar *aString,
-                          PRUint32 aRunStart,
-                          PRUint32 aRunLength,
-                          PRInt32 aRunScript)
+gfxGDIShaper::ShapeWord(gfxContext *aContext,
+                        gfxShapedWord *aShapedWord,
+                        const PRUnichar *aString)
 {
     DCFromContext dc(aContext);
     AutoSelectFont selectFont(dc, static_cast<gfxGDIFont*>(mFont)->GetHFONT());
 
+    PRUint32 length = aShapedWord->Length();
     nsAutoTArray<WORD,500> glyphArray;
-    if (!glyphArray.SetLength(aRunLength)) {
+    if (!glyphArray.SetLength(length)) {
         return false;
     }
     WORD *glyphs = glyphArray.Elements();
 
-    DWORD ret = ::GetGlyphIndicesW(dc, aString + aRunStart, aRunLength,
+    DWORD ret = ::GetGlyphIndicesW(dc, aString, length,
                                    glyphs, GGI_MARK_NONEXISTING_GLYPHS);
     if (ret == GDI_ERROR) {
         return false;
     }
 
-    for (int k = 0; k < aRunLength; k++) {
+    for (int k = 0; k < length; k++) {
         if (glyphs[k] == 0xFFFF)
             return false;
     }
  
     SIZE size;
     nsAutoTArray<int,500> partialWidthArray;
-    if (!partialWidthArray.SetLength(aRunLength)) {
+    if (!partialWidthArray.SetLength(length)) {
         return false;
     }
 
     BOOL success = ::GetTextExtentExPointI(dc,
                                            glyphs,
-                                           aRunLength,
+                                           length,
                                            INT_MAX,
                                            NULL,
                                            partialWidthArray.Elements(),
                                            &size);
     if (!success) {
         return false;
     }
 
     gfxTextRun::CompressedGlyph g;
     PRUint32 i;
     PRInt32 lastWidth = 0;
-    PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit();
-    for (i = 0; i < aRunLength; ++i) {
-        PRUint32 offset = aRunStart + i;
+    PRUint32 appUnitsPerDevPixel = aShapedWord->AppUnitsPerDevUnit();
+    for (i = 0; i < length; ++i) {
+        PRUint32 offset = i;
         PRInt32 advancePixels = partialWidthArray[i] - lastWidth;
         lastWidth = partialWidthArray[i];
-        PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel;
+        PRInt32 advanceAppUnits = advancePixels * appUnitsPerDevPixel;
         WCHAR glyph = glyphs[i];
-        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aTextRun->GetChar(offset)),
+        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aShapedWord->GetCharAt(offset)),
                      "Invalid character detected!");
-        bool atClusterStart = aTextRun->IsClusterStart(offset);
+        bool atClusterStart = aShapedWord->IsClusterStart(offset);
         if (advanceAppUnits >= 0 &&
-            gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
-            gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(glyph) &&
             atClusterStart)
         {
-            aTextRun->SetSimpleGlyph(offset,
-                                     g.SetSimpleGlyph(advanceAppUnits, glyph));
+            aShapedWord->SetSimpleGlyph(offset,
+                                        g.SetSimpleGlyph(advanceAppUnits, glyph));
         } else {
-            gfxTextRun::DetailedGlyph details;
+            gfxShapedWord::DetailedGlyph details;
             details.mGlyphID = glyph;
             details.mAdvance = advanceAppUnits;
             details.mXOffset = 0;
             details.mYOffset = 0;
-            aTextRun->SetGlyphs(offset,
-                                g.SetComplex(atClusterStart, true, 1),
-                                &details);
+            aShapedWord->SetGlyphs(offset,
+                                   g.SetComplex(atClusterStart, true, 1),
+                                   &details);
         }
     }
 
     return true;
 }
--- a/gfx/thebes/gfxGDIShaper.h
+++ b/gfx/thebes/gfxGDIShaper.h
@@ -49,17 +49,14 @@ public:
         MOZ_COUNT_CTOR(gfxGDIShaper);
     }
 
     virtual ~gfxGDIShaper()
     {
         MOZ_COUNT_DTOR(gfxGDIShaper);
     }
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aString);
 };
 
 #endif /* GFX_GDISHAPER_H */
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -156,22 +156,19 @@ MakeGraphiteLangTag(PRUint32 aTag)
     while ((grLangTag & mask) == ' ') {
         grLangTag &= ~mask;
         mask <<= 8;
     }
     return grLangTag;
 }
 
 bool
-gfxGraphiteShaper::InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript)
+gfxGraphiteShaper::ShapeWord(gfxContext      *aContext,
+                             gfxShapedWord   *aShapedWord,
+                             const PRUnichar *aText)
 {
     // some font back-ends require this in order to get proper hinted metrics
     mFont->SetupCairoFont(aContext);
 
     mCallbackData.mContext = aContext;
 
     if (!mGrFont) {
         mGrFace = gr_make_face(&mCallbackData, GrGetTable, gr_face_default);
@@ -185,73 +182,61 @@ gfxGraphiteShaper::InitTextRun(gfxContex
             gr_make_font(mFont->GetAdjustedSize(), mGrFace);
         if (!mGrFont) {
             gr_face_destroy(mGrFace);
             mGrFace = nsnull;
             return false;
         }
     }
 
-    const gfxFontStyle *style = aTextRun->GetFontGroup()->GetStyle();
+    gfxFontEntry *entry = mFont->GetFontEntry();
+    const gfxFontStyle *style = mFont->GetStyle();
     PRUint32 grLang = 0;
     if (style->languageOverride) {
         grLang = MakeGraphiteLangTag(style->languageOverride);
-    } else if (mFont->GetFontEntry()->mLanguageOverride) {
-        grLang = MakeGraphiteLangTag(mFont->GetFontEntry()->mLanguageOverride);
+    } else if (entry->mLanguageOverride) {
+        grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
     } else {
         nsCAutoString langString;
         style->language->ToUTF8String(langString);
         grLang = GetGraphiteTagForLang(langString);
     }
     gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
 
-    bool disableLigatures =
-        (aTextRun->GetFlags() &
-         gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
-    if (disableLigatures) {
+    if (aShapedWord->DisableLigatures()) {
         const gr_feature_ref* fref =
             gr_face_find_fref(mGrFace, TRUETYPE_TAG('l','i','g','a'));
         if (fref) {
             gr_fref_set_feature_value(fref, 0, grFeatures);
         }
     }
 
     const nsTArray<gfxFontFeature> *features = &style->featureSettings;
     if (features->IsEmpty()) {
-        features = &mFont->GetFontEntry()->mFeatureSettings;
+        features = &entry->mFeatureSettings;
     }
     for (PRUint32 i = 0; i < features->Length(); ++i) {
         const gr_feature_ref* fref =
             gr_face_find_fref(mGrFace, (*features)[i].mTag);
         if (fref) {
             gr_fref_set_feature_value(fref, (*features)[i].mValue, grFeatures);
         }
     }
 
-    const PRUnichar *textStart = aString + aRunStart;
-    const PRUnichar *textEnd = textStart + aRunLength;
-    const void *pError;
-    size_t nChars = gr_count_unicode_characters(gr_utf16,
-                                                textStart, textEnd,
-                                                &pError);
-    if (pError != nsnull) {
-        return false;
-    }
-
     gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
-                                  gr_utf16, textStart, nChars,
-                                  aTextRun->IsRightToLeft());
+                                  gr_utf16, aText, aShapedWord->Length(),
+                                  aShapedWord->IsRightToLeft());
     if (features) {
         gr_featureval_destroy(grFeatures);
     }
     if (!seg) {
         return false;
     }
 
-    nsresult rv = SetGlyphsFromSegment(aTextRun, aRunStart, aRunLength, seg);
+    nsresult rv = SetGlyphsFromSegment(aShapedWord, seg);
 
     gr_seg_destroy(seg);
 
     return NS_SUCCEEDED(rv);
 }
 
 #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
                             // for short (typical) runs up to this length
@@ -260,33 +245,31 @@ struct Cluster {
     PRUint32 baseChar;
     PRUint32 baseGlyph;
     PRUint32 nChars;
     PRUint32 nGlyphs;
     Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
 };
 
 nsresult
-gfxGraphiteShaper::SetGlyphsFromSegment(gfxTextRun *aTextRun,
-                                        PRUint32 aRunStart,
-                                        PRUint32 aRunLength,
+gfxGraphiteShaper::SetGlyphsFromSegment(gfxShapedWord *aShapedWord,
                                         gr_segment *aSegment)
 {
-    PRInt32 dev2appUnits = aTextRun->GetAppUnitsPerDevUnit();
-    bool rtl = aTextRun->IsRightToLeft();
+    PRInt32 dev2appUnits = aShapedWord->AppUnitsPerDevUnit();
+    bool rtl = aShapedWord->IsRightToLeft();
 
     PRUint32 glyphCount = gr_seg_n_slots(aSegment);
 
     // identify clusters; graphite may have reordered/expanded/ligated glyphs.
     nsAutoTArray<Cluster,SMALL_GLYPH_RUN> clusters;
     nsAutoTArray<PRUint16,SMALL_GLYPH_RUN> gids;
     nsAutoTArray<float,SMALL_GLYPH_RUN> xLocs;
     nsAutoTArray<float,SMALL_GLYPH_RUN> yLocs;
 
-    if (!clusters.SetLength(aRunLength) ||
+    if (!clusters.SetLength(aShapedWord->Length()) ||
         !gids.SetLength(glyphCount) ||
         !xLocs.SetLength(glyphCount) ||
         !yLocs.SetLength(glyphCount))
     {
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // walk through the glyph slots and check which original character
@@ -312,27 +295,27 @@ gfxGraphiteShaper::SetGlyphsFromSegment(
             --cIndex;
         }
 
         // if there's a gap between the current cluster's base character and
         // this glyph's, extend the cluster to include the intervening chars
         if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars &&
             before >= clusters[cIndex].baseChar + clusters[cIndex].nChars)
         {
-            NS_ASSERTION(cIndex < aRunLength - 1, "cIndex at end of run");
+            NS_ASSERTION(cIndex < aShapedWord->Length() - 1, "cIndex at end of word");
             Cluster& c = clusters[cIndex + 1];
             c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars;
             c.nChars = before - c.baseChar;
             c.baseGlyph = gIndex;
             c.nGlyphs = 0;
             ++cIndex;
         }
 
         // increment cluster's glyph count to include current slot
-        NS_ASSERTION(cIndex < aRunLength, "cIndex beyond valid run length");
+        NS_ASSERTION(cIndex < aShapedWord->Length(), "cIndex beyond word length");
         ++clusters[cIndex].nGlyphs;
 
         // extend cluster if necessary to reach the glyph's "after" index
         if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) {
             clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar;
         }
     }
 
@@ -353,65 +336,64 @@ gfxGraphiteShaper::SetGlyphsFromSegment(
             } else {
                 adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph];
             }
         }
 
         // Check for default-ignorable char that didn't get filtered, combined,
         // etc by the shaping process, and skip it.
         PRUint32 offs = gr_cinfo_base(gr_seg_cinfo(aSegment, c.baseChar));
-        NS_ASSERTION(offs >= c.baseChar && offs < aRunLength,
+        NS_ASSERTION(offs >= c.baseChar && offs < aShapedWord->Length(),
                      "unexpected offset");
         if (c.nGlyphs == 1 && c.nChars == 1 &&
-            aTextRun->FilterIfIgnorable(aRunStart + offs))
+            aShapedWord->FilterIfIgnorable(offs))
         {
             continue;
         }
 
         PRUint32 appAdvance = adv * dev2appUnits;
         if (c.nGlyphs == 1 &&
-            gfxTextRun::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
-            gfxTextRun::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
             yLocs[c.baseGlyph] == 0)
         {
-            gfxTextRun::CompressedGlyph g;
-            aTextRun->SetSimpleGlyph(aRunStart + offs,
-                                     g.SetSimpleGlyph(appAdvance,
-                                                      gids[c.baseGlyph]));
+            gfxShapedWord::CompressedGlyph g;
+            aShapedWord->SetSimpleGlyph(offs,
+                                        g.SetSimpleGlyph(appAdvance,
+                                                         gids[c.baseGlyph]));
         } else {
             // not a one-to-one mapping with simple metrics: use DetailedGlyph
-            nsAutoTArray<gfxTextRun::DetailedGlyph,8> details;
+            nsAutoTArray<gfxShapedWord::DetailedGlyph,8> details;
             float clusterLoc;
             for (PRUint32 j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
-                gfxTextRun::DetailedGlyph* d = details.AppendElement();
+                gfxShapedWord::DetailedGlyph* d = details.AppendElement();
                 d->mGlyphID = gids[j];
                 d->mYOffset = -yLocs[j] * dev2appUnits;
                 if (j == c.baseGlyph) {
                     d->mXOffset = 0;
                     d->mAdvance = appAdvance;
                     clusterLoc = xLocs[j];
                 } else {
                     d->mXOffset = (xLocs[j] - clusterLoc - adv) * dev2appUnits;
                     d->mAdvance = 0;
                 }
             }
-            gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(aRunStart + offs),
+            gfxShapedWord::CompressedGlyph g;
+            g.SetComplex(aShapedWord->IsClusterStart(offs),
                          true, details.Length());
-            aTextRun->SetGlyphs(aRunStart + offs, g, details.Elements());
+            aShapedWord->SetGlyphs(offs, g, details.Elements());
         }
 
         for (PRUint32 j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) {
             offs = gr_cinfo_base(gr_seg_cinfo(aSegment, j));
-            NS_ASSERTION(offs >= j && offs < aRunLength,
+            NS_ASSERTION(offs >= j && offs < aShapedWord->Length(),
                          "unexpected offset");
-            gfxTextRun::CompressedGlyph g;
-            g.SetComplex(aTextRun->IsClusterStart(aRunStart + offs),
-                         false, 0);
-            aTextRun->SetGlyphs(aRunStart + offs, g, nsnull);
+            gfxShapedWord::CompressedGlyph g;
+            g.SetComplex(aShapedWord->IsClusterStart(offs), false, 0);
+            aShapedWord->SetGlyphs(offs, g, nsnull);
         }
     }
 
     return NS_OK;
 }
 
 // for language tag validation - include list of tags from the IANA registry
 #include "gfxLanguageTagList.cpp"
--- a/gfx/thebes/gfxGraphiteShaper.h
+++ b/gfx/thebes/gfxGraphiteShaper.h
@@ -47,22 +47,19 @@ class gr_face;
 class gr_font;
 class gr_segment;
 
 class gfxGraphiteShaper : public gfxFontShaper {
 public:
     gfxGraphiteShaper(gfxFont *aFont);
     virtual ~gfxGraphiteShaper();
 
-    virtual bool InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript);
+    virtual bool ShapeWord(gfxContext *aContext,
+                           gfxShapedWord *aShapedWord,
+                           const PRUnichar *aText);
 
     const void* GetTable(PRUint32 aTag, size_t *aLength);
 
     static void Shutdown();
 
     struct CallbackData {
         gfxFont           *mFont;
         gfxGraphiteShaper *mShaper;
@@ -71,19 +68,17 @@ public:
 
     struct TableRec {
         hb_blob_t  *mBlob;
         const void *mData;
         PRUint32    mLength;
     };
 
 protected:
-    nsresult SetGlyphsFromSegment(gfxTextRun *aTextRun,
-                                  PRUint32 aRunStart,
-                                  PRUint32 aRunLength,
+    nsresult SetGlyphsFromSegment(gfxShapedWord *aShapedWord,
                                   gr_segment *aSegment);
 
     gr_face *mGrFace;
     gr_font *mGrFont;
 
     CallbackData mCallbackData;
 
     nsDataHashtable<nsUint32HashKey,TableRec> mTables;
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -708,22 +708,19 @@ HBGetEastAsianWidth(hb_codepoint_t aCh)
 /*
  * gfxFontShaper override to initialize the text run using HarfBuzz
  */
 
 static hb_font_funcs_t * sHBFontFuncs = nsnull;
 static hb_unicode_funcs_t * sHBUnicodeFuncs = nsnull;
 
 bool
-gfxHarfBuzzShaper::InitTextRun(gfxContext *aContext,
-                               gfxTextRun *aTextRun,
-                               const PRUnichar *aString,
-                               PRUint32 aRunStart,
-                               PRUint32 aRunLength,
-                               PRInt32 aRunScript)
+gfxHarfBuzzShaper::ShapeWord(gfxContext      *aContext,
+                             gfxShapedWord   *aShapedWord,
+                             const PRUnichar *aText)
 {
     // some font back-ends require this in order to get proper hinted metrics
     mFont->SetupCairoFont(aContext);
 
     if (!mHBFace) {
 
         mUseFontGlyphWidths = mFont->ProvidesGlyphWidths();
 
@@ -813,92 +810,87 @@ gfxHarfBuzzShaper::InitTextRun(gfxContex
 
     FontCallbackData fcd(this, aContext);
     hb_font_t *font = hb_font_create();
     hb_font_set_funcs(font, sHBFontFuncs, nsnull, &fcd);
     hb_font_set_ppem(font, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
     PRUint32 scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
     hb_font_set_scale(font, scale, scale);
 
-    // aRunStart and aRunLength define the section of the textRun and of
-    // aString that is to be drawn with this particular font
-
-    bool disableLigatures =
-        (aTextRun->GetFlags() &
-         gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
-
     nsAutoTArray<hb_feature_t,20> features;
 
     // Ligature features are enabled by default in the generic shaper,
     // so we explicitly turn them off if necessary (for letter-spacing)
-    if (disableLigatures) {
+    if (aShapedWord->DisableLigatures()) {
         hb_feature_t ligaOff = { HB_TAG('l','i','g','a'), 0, 0, UINT_MAX };
         hb_feature_t cligOff = { HB_TAG('c','l','i','g'), 0, 0, UINT_MAX };
         features.AppendElement(ligaOff);
         features.AppendElement(cligOff);
     }
 
     // css features need to be merged with the existing ones, if any
-    const gfxFontStyle *style = aTextRun->GetFontGroup()->GetStyle();
+    gfxFontEntry *entry = mFont->GetFontEntry();
+    const gfxFontStyle *style = mFont->GetStyle();
     const nsTArray<gfxFontFeature> *cssFeatures = &style->featureSettings;
     if (cssFeatures->IsEmpty()) {
-        cssFeatures = &mFont->GetFontEntry()->mFeatureSettings;
+        cssFeatures = &entry->mFeatureSettings;
     }
     for (PRUint32 i = 0; i < cssFeatures->Length(); ++i) {
         PRUint32 j;
         for (j = 0; j < features.Length(); ++j) {
             if (cssFeatures->ElementAt(i).mTag == features[j].tag) {
                 features[j].value = cssFeatures->ElementAt(i).mValue;
                 break;
             }
         }
         if (j == features.Length()) {
             const gfxFontFeature& f = cssFeatures->ElementAt(i);
             hb_feature_t hbf = { f.mTag, f.mValue, 0, UINT_MAX };
             features.AppendElement(hbf);
         }
     }
 
-    hb_buffer_t *buffer = hb_buffer_create(aRunLength);
+    bool isRightToLeft = aShapedWord->IsRightToLeft();
+    hb_buffer_t *buffer = hb_buffer_create(aShapedWord->Length());
     hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs);
-    hb_buffer_set_direction(buffer,
-                            aTextRun->IsRightToLeft() ?
-                                HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+    hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL :
+                                                    HB_DIRECTION_LTR);
     // For unresolved "common" or "inherited" runs, default to Latin for now.
     // (Should we somehow use the language or locale to try and infer
     // a better default?)
     hb_buffer_set_script(buffer,
-                         aRunScript <= HB_SCRIPT_INHERITED ? HB_SCRIPT_LATIN
-                         : hb_script_t(aRunScript));
+                         aShapedWord->Script() <= HB_SCRIPT_INHERITED ?
+                             HB_SCRIPT_LATIN :
+                             hb_script_t(aShapedWord->Script()));
 
     hb_language_t language;
     if (style->languageOverride) {
         language = hb_ot_tag_to_language(style->languageOverride);
-    } else if (mFont->GetFontEntry()->mLanguageOverride) {
-        language =
-            hb_ot_tag_to_language(mFont->GetFontEntry()->mLanguageOverride);
+    } else if (entry->mLanguageOverride) {
+        language = hb_ot_tag_to_language(entry->mLanguageOverride);
     } else {
         nsCString langString;
         style->language->ToUTF8String(langString);
         language = hb_language_from_string(langString.get());
     }
     hb_buffer_set_language(buffer, language);
 
-    hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t*>(aString + aRunStart),
-                        aRunLength, 0, aRunLength);
+    PRUint32 length = aShapedWord->Length();
+    hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t*>(aText),
+                        length, 0, length);
 
     hb_shape(font, mHBFace, buffer, features.Elements(), features.Length());
 
-    if (aTextRun->IsRightToLeft()) {
+    if (isRightToLeft) {
         hb_buffer_reverse(buffer);
     }
 
-    nsresult rv =
-        SetGlyphsFromRun(aContext, aTextRun, buffer, aRunStart, aRunLength);
-    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into textrun");
+    nsre