Support for word-wrap CSS property. Bug 99457, r+sr=dbaron, roc
authorSimon Montagu <smontagu@smontagu.org>
Thu, 24 Jul 2008 10:16:18 +0300
changeset 16166 3cf51abb4040135b42a3a8a4f6043532aaf8cde5
parent 16165 5daf2445b8b4052b7d60a9f63c4c2f864442243d
child 16167 2d36444242f231654401e6faffbf60a1c17ae698
push idunknown
push userunknown
push dateunknown
bugs99457
milestone1.9.1a2pre
Support for word-wrap CSS property. Bug 99457, r+sr=dbaron, roc
dom/public/idl/css/nsIDOMCSS2Properties.idl
gfx/thebes/public/gfxFont.h
gfx/thebes/public/gfxTypes.h
gfx/thebes/src/gfxFont.cpp
layout/base/nsStyleConsts.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsLineLayout.cpp
layout/generic/nsLineLayout.h
layout/generic/nsTextFrameThebes.cpp
layout/reftests/text/reftest.list
layout/reftests/text/wordwrap-01-ref.html
layout/reftests/text/wordwrap-01.html
layout/reftests/text/wordwrap-02-ref.html
layout/reftests/text/wordwrap-02.html
layout/reftests/text/wordwrap-03-ref.html
layout/reftests/text/wordwrap-03.html
layout/reftests/text/wordwrap-04-ref.html
layout/reftests/text/wordwrap-04.html
layout/reftests/text/wordwrap-05-ref.html
layout/reftests/text/wordwrap-05.html
layout/reftests/text/wordwrap-06-ref.html
layout/reftests/text/wordwrap-06.html
layout/style/forms.css
layout/style/nsCSSKeywordList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSStruct.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleContext.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
layout/style/viewsource.css
--- a/dom/public/idl/css/nsIDOMCSS2Properties.idl
+++ b/dom/public/idl/css/nsIDOMCSS2Properties.idl
@@ -401,17 +401,17 @@ interface nsIDOMCSS2Properties : nsISupp
 
            attribute DOMString        wordSpacing;
                                         // raises(DOMException) on setting
 
            attribute DOMString        zIndex;
                                         // raises(DOMException) on setting
 };
 
-[scriptable, uuid(5d8aab68-445b-4675-8661-8d722dbcb721)]
+[scriptable, uuid(216343fe-4e61-11dd-9843-001485f1fdbb)]
 interface nsIDOMNSCSS2Properties : nsIDOMCSS2Properties
 {
            /* Non-DOM 2 extensions */
 
            /* Mozilla extension CSS properties */
            attribute DOMString        MozAppearance;
                                         // raises(DOMException) on setting
 
@@ -606,9 +606,12 @@ interface nsIDOMNSCSS2Properties : nsIDO
            attribute DOMString        MozColumnRuleWidth;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozColumnRuleStyle;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozColumnRuleColor;
                                         // raises(DOMException) on setting
+
+           attribute DOMString        wordWrap;
+                                        // raises(DOMException) on setting
 };
--- a/gfx/thebes/public/gfxFont.h
+++ b/gfx/thebes/public/gfxFont.h
@@ -915,29 +915,38 @@ public:
      * @param aUsedHyphenation if non-null, records if we selected a hyphenation break
      * @param aLastBreak if non-null and result is aMaxLength, we set this to
      * the maximal N such that
      *       N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth
      *   OR  N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth
      * or PR_UINT32_MAX if no such N exists, where GetAdvanceWidth assumes
      * the effect of
      * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider)
+     *
+     * @param aCanWordWrap true if we can break between any two grapheme
+     * clusters. This is set by word-wrap: break-word
+     *
+     * @param aBreakPriority in/out the priority of the break opportunity
+     * saved in the line. If we are prioritizing break opportunities, we will
+     * not set a break with a lower priority. @see gfxBreakPriority.
      * 
      * Note that negative advance widths are possible especially if negative
      * spacing is provided.
      */
     PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
                                  PRBool aLineBreakBefore, gfxFloat aWidth,
                                  PropertyProvider *aProvider,
                                  PRBool aSuppressInitialBreak,
                                  gfxFloat *aTrimWhitespace,
                                  Metrics *aMetrics, PRBool aTightBoundingBox,
                                  gfxContext *aRefContextForTightBoundingBox,
                                  PRBool *aUsedHyphenation,
-                                 PRUint32 *aLastBreak);
+                                 PRUint32 *aLastBreak,
+                                 PRBool aCanWordWrap,
+                                 gfxBreakPriority *aBreakPriority);
 
     /**
      * Update the reference context.
      * XXX this is a hack. New text frame does not call this. Use only
      * temporarily for old text frame.
      */
     void SetContext(gfxContext *aContext) {}
 
--- a/gfx/thebes/public/gfxTypes.h
+++ b/gfx/thebes/public/gfxTypes.h
@@ -50,16 +50,41 @@ typedef double gfxFloat;
 # define THEBES_API
 #elif defined(IMPL_THEBES)
 # define THEBES_API NS_EXPORT
 #else
 # define THEBES_API NS_IMPORT
 #endif
 
 /**
+ * Priority of a line break opportunity.
+ *
+ * eNoBreak       The line has no break opportunities
+ * eWordWrapBreak The line has a break opportunity only within a word. With
+ *                word-wrap: break-word we will break at this point only if
+ *                there are no other break opportunities in the line.
+ * eNormalBreak   The line has a break opportunity determined by the standard
+ *                line-breaking algorithm.
+ *
+ * Future expansion: split eNormalBreak into multiple priorities, e.g.
+ *                    punctuation break and whitespace break (bug 389710).
+ *                   As and when we implement it, text-wrap: unrestricted will
+ *                    mean that priorities are ignored and all line-break
+ *                    opportunities are equal.
+ *
+ * @see gfxTextRun::BreakAndMeasureText
+ * @see nsLineLayout::NotifyOptionalBreakPosition
+ */
+enum gfxBreakPriority {
+    eNoBreak       = 0,
+    eWordWrapBreak,
+    eNormalBreak
+};
+
+/**
  * Define refcounting for Thebes.  For now use the stuff from nsISupportsImpl
  * even though it forces the functions to be virtual...
  */
 #include "nsISupportsImpl.h"
 #include "nsAutoPtr.h"
 
 #define THEBES_INLINE_DECL_REFCOUNTING(_class)                                \
 public:                                                                       \
--- a/gfx/thebes/src/gfxFont.cpp
+++ b/gfx/thebes/src/gfxFont.cpp
@@ -1681,17 +1681,19 @@ PRUint32
 gfxTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
                                 PRBool aLineBreakBefore, gfxFloat aWidth,
                                 PropertyProvider *aProvider,
                                 PRBool aSuppressInitialBreak,
                                 gfxFloat *aTrimWhitespace,
                                 Metrics *aMetrics, PRBool aTightBoundingBox,
                                 gfxContext *aRefContext,
                                 PRBool *aUsedHyphenation,
-                                PRUint32 *aLastBreak)
+                                PRUint32 *aLastBreak,
+                                PRBool aCanWordWrap,
+                                gfxBreakPriority *aBreakPriority)
 {
     aMaxLength = PR_MIN(aMaxLength, mCharacterCount - aStart);
 
     NS_ASSERTION(aStart + aMaxLength <= mCharacterCount, "Substring out of range");
 
     PRUint32 bufferStart = aStart;
     PRUint32 bufferLength = PR_MIN(aMaxLength, MEASUREMENT_BUFFER_SIZE);
     PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
@@ -1737,29 +1739,32 @@ gfxTextRun::BreakAndMeasureText(PRUint32
             if (haveHyphenation) {
                 aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
                                                 hyphenBuffer);
             }
         }
 
         PRBool lineBreakHere = mCharacterGlyphs[i].CanBreakBefore() &&
             (!aSuppressInitialBreak || i > aStart);
-        if (lineBreakHere || (haveHyphenation && hyphenBuffer[i - bufferStart])) {
+        PRBool hyphenation = haveHyphenation && hyphenBuffer[i - bufferStart];
+        PRBool wordWrapping = aCanWordWrap && *aBreakPriority <= eWordWrapBreak;
+        if (lineBreakHere || hyphenation || wordWrapping) {
             gfxFloat hyphenatedAdvance = advance;
-            PRBool hyphenation = !lineBreakHere;
-            if (hyphenation) {
+            if (!lineBreakHere && !wordWrapping) {
                 hyphenatedAdvance += aProvider->GetHyphenWidth();
             }
             
             if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
                 // We can break here.
                 lastBreak = i;
                 lastBreakTrimmableChars = trimmableChars;
                 lastBreakTrimmableAdvance = trimmableAdvance;
-                lastBreakUsedHyphenation = hyphenation;
+                lastBreakUsedHyphenation = !lineBreakHere && !wordWrapping;
+                *aBreakPriority = hyphenation || lineBreakHere ?
+                                   eNormalBreak : eWordWrapBreak;
             }
 
             width += advance;
             advance = 0;
             if (width - trimmableAdvance > aWidth) {
                 // No more text fits. Abort
                 aborted = PR_TRUE;
                 break;
--- a/layout/base/nsStyleConsts.h
+++ b/layout/base/nsStyleConsts.h
@@ -598,16 +598,20 @@
 
 // See nsStyleText
 #define NS_STYLE_WHITESPACE_NORMAL              0
 #define NS_STYLE_WHITESPACE_PRE                 1
 #define NS_STYLE_WHITESPACE_NOWRAP              2
 #define NS_STYLE_WHITESPACE_PRE_WRAP            3
 
 // See nsStyleText
+#define NS_STYLE_WORDWRAP_NORMAL                0
+#define NS_STYLE_WORDWRAP_BREAK_WORD            1
+
+// See nsStyleText
 #define NS_STYLE_UNICODE_BIDI_NORMAL            0
 #define NS_STYLE_UNICODE_BIDI_EMBED             1
 #define NS_STYLE_UNICODE_BIDI_OVERRIDE          2
 
 // See nsStyleTable (here for HTML 4.0 for now, should probably change to side flags)
 #define NS_STYLE_TABLE_FRAME_NONE               0
 #define NS_STYLE_TABLE_FRAME_ABOVE              1
 #define NS_STYLE_TABLE_FRAME_BELOW              2
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -3226,16 +3226,17 @@ nsBlockFrame::ReflowInlineFrames(nsBlock
   PRInt32 spins = 0;
 #endif
   LineReflowStatus lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
   PRBool movedPastFloat = PR_FALSE;
   do {
     PRBool allowPullUp = PR_TRUE;
     nsIContent* forceBreakInContent = nsnull;
     PRInt32 forceBreakOffset = -1;
+    gfxBreakPriority forceBreakPriority = eNoBreak;
     do {
       nsSpaceManager::SavedState spaceManagerState;
       aState.mReflowState.mSpaceManager->PushState(&spaceManagerState);
 
       // Once upon a time we allocated the first 30 nsLineLayout objects
       // on the stack, and then we switched to the heap.  At that time
       // these objects were large (1100 bytes on a 32 bit system).
       // Then the nsLineLayout object was shrunk to 156 bytes by
@@ -3256,17 +3257,17 @@ nsBlockFrame::ReflowInlineFrames(nsBlock
 
       if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus ||
           LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
         if (lineLayout.NeedsBackup()) {
           NS_ASSERTION(!forceBreakInContent, "Backing up twice; this should never be necessary");
           // If there is no saved break position, then this will set
           // set forceBreakInContent to null and we won't back up, which is
           // correct.
-          forceBreakInContent = lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset);
+          forceBreakInContent = lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset, &forceBreakPriority);
         } else {
           forceBreakInContent = nsnull;
         }
         // restore the space manager state
         aState.mReflowState.mSpaceManager->PopState(&spaceManagerState);
         // Clear out float lists
         aState.mCurrentLineFloats.DeleteAll();
         aState.mBelowCurrentLineFloats.DeleteAll();
@@ -3384,17 +3385,17 @@ nsBlockFrame::DoReflowInlineFrames(nsBlo
 
   // Determine whether this is a line of placeholders for out-of-flow
   // continuations
   PRBool isContinuingPlaceholders = PR_FALSE;
 
   if (impactedByFloats) {
     // There is a soft break opportunity at the start of the line, because
     // we can always move this line down below float(s).
-    if (aLineLayout.NotifyOptionalBreakPosition(frame->GetContent(), 0, PR_TRUE)) {
+    if (aLineLayout.NotifyOptionalBreakPosition(frame->GetContent(), 0, PR_TRUE, eNormalBreak)) {
       lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
     }
   }
 
   // need to repeatedly call GetChildCount here, because the child
   // count can change during the loop!
   for (i = 0; LINE_REFLOW_OK == lineReflowStatus && i < aLine->GetChildCount();
        i++, frame = frame->GetNextSibling()) {
@@ -3462,17 +3463,18 @@ nsBlockFrame::DoReflowInlineFrames(nsBlo
   	NS_WARNING("We shouldn't be backing up more than once! "
                "Someone must have set a break opportunity beyond the available width, "
                "even though there were better break opportunities before it");
     needsBackup = PR_FALSE;
   }
   if (needsBackup) {
     // We need to try backing up to before a text run
     PRInt32 offset;
-    nsIContent* breakContent = aLineLayout.GetLastOptionalBreakPosition(&offset);
+    gfxBreakPriority breakPriority;
+    nsIContent* breakContent = aLineLayout.GetLastOptionalBreakPosition(&offset, &breakPriority);
     // XXX It's possible, in fact not unusual, for the break opportunity to already
     // be the end of the line. We should detect that and optimize to not
     // re-do the line.
     if (breakContent) {
       // We can back up!
       lineReflowStatus = LINE_REFLOW_REDO_NO_PULL;
     }
   } else {
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -98,16 +98,17 @@ nsLineLayout::nsLineLayout(nsPresContext
                            const nsLineList::iterator* aLine)
   : mPresContext(aPresContext),
     mSpaceManager(aSpaceManager),
     mBlockReflowState(aOuterReflowState),
     mLastOptionalBreakContent(nsnull),
     mForceBreakContent(nsnull),
     mLastOptionalBreakContentOffset(-1),
     mForceBreakContentOffset(-1),
+    mLastOptionalBreakPriority(eNoBreak),
     mBlockRS(nsnull),/* XXX temporary */
     mMinLineHeight(0),
     mTextIndent(0)
 {
   NS_ASSERTION(aSpaceManager || aOuterReflowState->frame->GetType() ==
                                   nsGkAtoms::letterFrame,
                "space manager should be present");
   MOZ_COUNT_CTOR(nsLineLayout);
@@ -839,18 +840,20 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
   metrics.height = nscoord(0xdeadbeef);
 #endif
   nscoord tx = x - psd->mReflowState->mComputedBorderPadding.left;
   nscoord ty = y - psd->mReflowState->mComputedBorderPadding.top;
   mSpaceManager->Translate(tx, ty);
 
   nsIAtom* frameType = aFrame->GetType();
   PRInt32 savedOptionalBreakOffset;
+  gfxBreakPriority savedOptionalBreakPriority;
   nsIContent* savedOptionalBreakContent =
-    GetLastOptionalBreakPosition(&savedOptionalBreakOffset);
+    GetLastOptionalBreakPosition(&savedOptionalBreakOffset,
+                                 &savedOptionalBreakPriority);
 
   rv = aFrame->Reflow(mPresContext, metrics, reflowState, aReflowStatus);
   if (NS_FAILED(rv)) {
     NS_WARNING( "Reflow of frame failed in nsLineLayout" );
     return rv;
   }
   
   pfd->mJustificationNumSpaces = mTextJustificationNumSpaces;
@@ -1025,30 +1028,31 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
         VerticalAlignFrames(span);
       }
       
       if (!continuingTextRun) {
         if (!psd->mNoWrap && (!LineIsEmpty() || placedFloat)) {
           // record soft break opportunity after this content that can't be
           // part of a text run. This is not a text frame so we know
           // that offset PR_INT32_MAX means "after the content".
-          if (NotifyOptionalBreakPosition(aFrame->GetContent(), PR_INT32_MAX, optionalBreakAfterFits)) {
+          if (NotifyOptionalBreakPosition(aFrame->GetContent(), PR_INT32_MAX, optionalBreakAfterFits, eNormalBreak)) {
             // If this returns true then we are being told to actually break here.
             aReflowStatus = NS_INLINE_LINE_BREAK_AFTER(aReflowStatus);
           }
         }
       }
     }
     else {
       PushFrame(aFrame);
       aPushedFrame = PR_TRUE;
       // Undo any saved break positions that the frame might have told us about,
       // since we didn't end up placing it
       RestoreSavedBreakPosition(savedOptionalBreakContent,
-                                savedOptionalBreakOffset);
+                                savedOptionalBreakOffset,
+                                savedOptionalBreakPriority);
     }
   }
   else {
     PushFrame(aFrame);
   }
   
 #ifdef REALLY_NOISY_REFLOW
   nsFrame::IndentBy(stdout, mSpanDepth);
--- a/layout/generic/nsLineLayout.h
+++ b/layout/generic/nsLineLayout.h
@@ -50,16 +50,17 @@
 #ifndef nsLineLayout_h___
 #define nsLineLayout_h___
 
 #include "nsFrame.h"
 #include "nsDeque.h"
 #include "nsLineBox.h"
 #include "nsBlockReflowState.h"
 #include "plarena.h"
+#include "gfxTypes.h"
 
 class nsBlockFrame;
 
 class nsSpaceManager;
 class nsPlaceholderFrame;
 struct nsStyleText;
 
 class nsLineLayout {
@@ -268,53 +269,64 @@ public:
    * multiple frames, and the first frame fits on the line but the whole word
    * doesn't. We look back to the last optional break position and
    * reflow the whole line again, forcing a break at that position. The last
    * optional break position could be in a text frame or else after a frame
    * that cannot be part of a text run, so those are the positions we record.
    * 
    * @param aFits set to true if the break position is within the available width.
    * 
+   * @param aPriority the priority of the break opportunity. If we are
+   * prioritizing break opportunities, we will not set a break if we have
+   * already set a break with a higher priority. @see gfxBreakPriority.
+   *
    * @return PR_TRUE if we are actually reflowing with forced break position and we
    * should break here
    */
   PRBool NotifyOptionalBreakPosition(nsIContent* aContent, PRInt32 aOffset,
-                                     PRBool aFits) {
+                                     PRBool aFits, gfxBreakPriority aPriority) {
     NS_ASSERTION(!aFits || !GetFlag(LL_NEEDBACKUP),
                   "Shouldn't be updating the break position with a break that fits after we've already flagged an overrun");
     // Remember the last break position that fits; if there was no break that fit,
     // just remember the first break
-    if (aFits || !mLastOptionalBreakContent) {
+    if ((aFits && aPriority >= mLastOptionalBreakPriority) ||
+        !mLastOptionalBreakContent) {
       mLastOptionalBreakContent = aContent;
       mLastOptionalBreakContentOffset = aOffset;
+      mLastOptionalBreakPriority = aPriority;
     }
     return aContent && mForceBreakContent == aContent &&
       mForceBreakContentOffset == aOffset;
   }
   /**
    * Like NotifyOptionalBreakPosition, but here it's OK for LL_NEEDBACKUP
    * to be set, because the caller is merely pruning some saved break position(s)
    * that are actually not feasible.
    */
-  void RestoreSavedBreakPosition(nsIContent* aContent, PRInt32 aOffset) {
+  void RestoreSavedBreakPosition(nsIContent* aContent, PRInt32 aOffset,
+                                 gfxBreakPriority aPriority) {
     mLastOptionalBreakContent = aContent;
     mLastOptionalBreakContentOffset = aOffset;
+    mLastOptionalBreakPriority = aPriority;
   }
   /**
    * Signal that no backing up will be required after all.
    */
   void ClearOptionalBreakPosition() {
     SetFlag(LL_NEEDBACKUP, PR_FALSE);
     mLastOptionalBreakContent = nsnull;
     mLastOptionalBreakContentOffset = -1;
+    mLastOptionalBreakPriority = eNoBreak;
   }
   // Retrieve last set optional break position. When this returns null, no
   // optional break has been recorded (which means that the line can't break yet).
-  nsIContent* GetLastOptionalBreakPosition(PRInt32* aOffset) {
+  nsIContent* GetLastOptionalBreakPosition(PRInt32* aOffset,
+                                           gfxBreakPriority* aPriority) {
     *aOffset = mLastOptionalBreakContentOffset;
+    *aPriority = mLastOptionalBreakPriority;
     return mLastOptionalBreakContent;
   }
   
   /**
    * Check whether frames overflowed the available width and CanPlaceFrame
    * requested backing up to a saved break position.
    */  
   PRBool NeedsBackup() { return GetFlag(LL_NEEDBACKUP); }
@@ -364,16 +376,17 @@ protected:
   nsSpaceManager* mSpaceManager;
   const nsStyleText* mStyleText; // for the block
   const nsHTMLReflowState* mBlockReflowState;
 
   nsIContent* mLastOptionalBreakContent;
   nsIContent* mForceBreakContent;
   PRInt32     mLastOptionalBreakContentOffset;
   PRInt32     mForceBreakContentOffset;
+  gfxBreakPriority mLastOptionalBreakPriority;
   
   // XXX remove this when landing bug 154892 (splitting absolute positioned frames)
   friend class nsInlineFrame;
 
   nsBlockReflowState* mBlockRS;/* XXX hack! */
   nscoord mMinLineHeight;
   PRUint8 mTextAlign;
 
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -5622,24 +5622,28 @@ nsTextFrame::Reflow(nsPresContext*      
     iter.SetOriginalOffset(offset + limitLength);
     transformedLength = iter.GetSkippedOffset() - transformedOffset;
   }
   PRUint32 transformedLastBreak = 0;
   PRBool usedHyphenation;
   gfxFloat trimmedWidth = 0;
   gfxFloat availWidth = aReflowState.availableWidth;
   PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
+  PRInt32 unusedOffset;  
+  gfxBreakPriority breakPriority;
+  lineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
   PRUint32 transformedCharsFit =
     mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
                                   (GetStateBits() & TEXT_START_OF_LINE) != 0,
                                   availWidth,
                                   &provider, !lineLayout.LineIsBreakable(),
                                   canTrimTrailingWhitespace ? &trimmedWidth : nsnull,
                                   &textMetrics, needTightBoundingBox, ctx,
-                                  &usedHyphenation, &transformedLastBreak);
+                                  &usedHyphenation, &transformedLastBreak,
+                                  textStyle->WordCanWrap(), &breakPriority);
   // The "end" iterator points to the first character after the string mapped
   // by this frame. Basically, its original-string offset is offset+charsFit
   // after we've computed charsFit.
   gfxSkipCharsIterator end(provider.GetEndHint());
   end.SetSkippedOffset(transformedOffset + transformedCharsFit);
   PRInt32 charsFit = end.GetOriginalOffset() - offset;
   if (offset + charsFit == newLineOffset) {
     // We broke before a trailing preformatted '\n'. The newline should
@@ -5704,17 +5708,17 @@ nsTextFrame::Reflow(nsPresContext*      
     }
   }
 
   if (!brokeText && lastBreak >= 0) {
     // Since everything fit and no break was forced,
     // record the last break opportunity
     NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aReflowState.availableWidth,
                  "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
-    lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, PR_TRUE);
+    lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, PR_TRUE, breakPriority);
   }
 
   PRInt32 contentLength = offset + charsFit - GetContentOffset();
 
   /////////////////////////////////////////////////////////////////////
   // Compute output metrics
   /////////////////////////////////////////////////////////////////////
 
@@ -5773,34 +5777,36 @@ nsTextFrame::Reflow(nsPresContext*      
   if (transformedCharsFit > 0) {
     lineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
     AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
   }
   if (charsFit > 0 && charsFit == length &&
       HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
     // Record a potential break after final soft hyphen
     lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
-        textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth);
+        textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth,
+                                           eNormalBreak);
   }
   PRBool breakAfter = forceBreakAfter;
   if (!breakAfter && charsFit == length &&
       transformedOffset + transformedLength == mTextRun->GetLength() &&
       (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) {
     // We placed all the text in the textrun and we have a break opportunity at
     // the end of the textrun. We need to record it because the following
     // content may not care about nsLineBreaker.
 
     // Note that because we didn't break, we can be sure that (thanks to the
     // code up above) textMetrics.mAdvanceWidth includes the width of any
     // trailing whitespace. So we need to subtract trimmableWidth here
     // because if we did break at this point, that much width would be trimmed.
     if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
       breakAfter = PR_TRUE;
     } else {
-      lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, PR_TRUE);
+      lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, PR_TRUE,
+                                             eNormalBreak);
     }
   }
   if (completedFirstLetter) {
     lineLayout.SetFirstLetterStyleOK(PR_FALSE);
   }
 
   // Compute reflow status
   aStatus = contentLength == maxContentLength
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -1,11 +1,17 @@
 == long-1.html long-ref.html
 random == soft-hyphens-1a.html soft-hyphens-1-ref.html # bug 406299
 random == soft-hyphens-1b.html soft-hyphens-1-ref.html # bug 406299
 random == soft-hyphens-1c.html soft-hyphens-1-ref.html # bug 406299
 == white-space-1a.html white-space-1-ref.html
 == white-space-1b.html white-space-1-ref.html
 == white-space-2.html white-space-2-ref.html
+== wordwrap-01.html wordwrap-01-ref.html
+== wordwrap-02.html wordwrap-02-ref.html
+== wordwrap-03.html wordwrap-03-ref.html
+== wordwrap-04.html wordwrap-04-ref.html
+== wordwrap-05.html wordwrap-05-ref.html
+== wordwrap-06.html wordwrap-06-ref.html
 == zwnj-01.html zwnj-01-ref.html
 == zwnj-02.html zwnj-02-ref.html
 random-if(MOZ_WIDGET_TOOLKIT=="gtk2") != zwnj-01.html zwnj-02-ref.html # Bad fonts on the tinderbox -- works locally
 random == 444656.html 444656-ref.html # bug 406299
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-01-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <style type="text/css">
+textarea { overflow: scroll; }
+  </style>
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <textarea rows="10" cols="20">It's
+lipsmackinthirstquen
+chinacetastinmotivat
+ingoodbuzzincooltalk
+inhighwalkinfastlivi
+nevergivincoolfizzin
+ Firefox!</textarea>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-01.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <style type="text/css">
+textarea { overflow: scroll; }
+  </style>
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <textarea rows="10" cols="20">It's lipsmackinthirstquenchinacetastinmotivatingoodbuzzincooltalkinhighwalkinfastlivinevergivincoolfizzin Firefox!</textarea>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-02-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<!-- tests that Arabic characters shape across word breaks -->
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <style type="text/css">
+textarea { overflow: scroll; }
+  </style>
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <textarea rows="10" cols="20" dir="rtl">ذهعقروبلجيكا،الموسوع&zwj;
+&zwj;ةكل,تمبوابةاقتصاديةه&zwj;
+&zwj;ذه.ضمنهاالروسوحرمانب&zwj;
+&zwj;لعدد,يكنجسيمةلإعادةلم
+&zwj;.يكنوالحلفاءبالقنابل&zwj;
+&zwj;هو,بحثخسائرالدفاعبال&zwj;
+&zwj;هجومعن.فرنسيةمارشالب&zwj;
+&zwj;ينيتودحرثم,وصلبشريةا
+لرايخبالحربتم.</textarea>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-02.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!-- tests that Arabic characters shape across word breaks -->
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <style type="text/css">
+textarea { overflow: scroll; }
+  </style>
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <textarea rows="10" cols="20" dir="rtl">ذهعقروبلجيكا،الموسوعةكل,تمبوابةاقتصاديةهذه.ضمنهاالروسوحرمانبلعدد,يكنجسيمةلإعادةلم.يكنوالحلفاءبالقنابلهو,بحثخسائرالدفاعبالهجومعن.فرنسيةمارشالبينيتودحرثم,وصلبشريةالرايخبالحربتم.</textarea>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-03-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!-- tests that words don't break in mid-cluster -->
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <style type="text/css">
+textarea { overflow: scroll; }
+  </style>
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <textarea rows="10" cols="27">It's
+lipsmackinthirstquenchinace&#x300;
+tastinmotivatingoodbuzzinco&#x301;
+oltalkinhighwalkinfastlivin&#x303;
+evergivincoolfizzin
+Firefox!</textarea>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-03.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!-- tests that words don't break in mid-cluster -->
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <style type="text/css">
+textarea { overflow: scroll; }
+  </style>
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <textarea rows="10" cols="27">It's lipsmackinthirstquenchinace&#x300;tastinmotivatingoodbuzzinco&#x301;oltalkinhighwalkinfastlivin&#x303;evergivincoolfizzin Firefox!</textarea>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-04-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <p style="width: 100px; word-wrap: break-word;">It's lipsmackinthirstquenchinacetastinmotivatingoodbuzzincooltalkinhighwalkinfastlivinevergivincoolfizzin Firefox!</p>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-04.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!-- Test setting word-wrap: break-word dynamically -->
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <title>Test Wordwrap</title>
+  <script type="text/javascript">
+function SwapStyle()
+{
+  par = document.getElementById("pp");
+  par.style.wordWrap = "break-word";
+}
+  </script>
+ </head>
+ <body onload="SwapStyle()">
+  <p id="pp" style="width: 100px; word-wrap: normal;">It's lipsmackinthirstquenchinacetastinmotivatingoodbuzzincooltalkinhighwalkinfastlivinevergivincoolfizzin Firefox!</p>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-05-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <title>Test Wordwrap</title>
+ </head>
+ <body>
+  <p style="width: 100px;">It's lipsmackinthirstquenchinacetastinmotivatingoodbuzzincooltalkinhighwalkinfastlivinevergivincoolfizzin Firefox!</p>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-05.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!-- Test unsetting word-wrap: break-word dynamically -->
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <title>Test Wordwrap</title>
+  <script type="text/javascript">
+function SwapStyle()
+{
+  par = document.getElementById("pp");
+  par.style.wordWrap = "normal";
+}
+  </script>
+ </head>
+ <body onload="SwapStyle()">
+  <p id="pp" style="width: 100px; word-wrap: break-word;">It's lipsmackinthirstquenchinacetastinmotivatingoodbuzzincooltalkinhighwalkinfastlivinevergivincoolfizzin Firefox!</p>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-06-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <title>http://www.mozilla.org/projects/minefield/</title>
+  <style type="text/css">
+#viewsource {
+  font-family: -moz-fixed;
+  font-weight: normal;
+  font-size: 16px;
+  color: black;
+  white-space: pre;
+}
+pre {
+  font: inherit;
+  color: inherit;
+  white-space: inherit; 
+  margin: 0;
+  width: 475px;
+}
+.attribute-name {
+ color: black;
+ font-weight: bold;
+}
+.attribute-value {
+ color: blue;
+ font-weight: normal;
+}
+  </style>
+ </head>
+ <body id="viewsource">
+  <pre id="line1"><span class="attribute-name">href</span>=<span class="attribute-value">"../../css/cavendish/content.css" </span>
+<span class="attribute-name">title</span>=<span class="attribute-value">"Cavendish"</span></pre>
+ </body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/wordwrap-06.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <title>http://www.mozilla.org/projects/minefield/</title>
+  <style type="text/css">
+#viewsource {
+  font-family: -moz-fixed;
+  font-weight: normal;
+  font-size: 16px;
+  color: black;
+  white-space: pre;
+}
+#viewsource.wrap {
+  white-space: pre-wrap;
+  word-wrap: break-word;
+}
+pre {
+  font: inherit;
+  color: inherit;
+  white-space: inherit; 
+  margin: 0;
+  width: 475px;
+}
+.attribute-name {
+ color: black;
+ font-weight: bold;
+}
+.attribute-value {
+ color: blue;
+ font-weight: normal;
+}
+  </style>
+ </head>
+ <body id="viewsource" class="wrap">
+  <pre id="line1"><span class="attribute-name">href</span>=<span class="attribute-value">"../../css/cavendish/content.css" </span><span class="attribute-name">title</span>=<span class="attribute-value">"Cavendish"</span></pre>
+ </body>
+</html>
\ No newline at end of file
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -121,16 +121,17 @@ textarea {
   letter-spacing: normal;
   vertical-align: text-bottom;
   cursor: text;
   -moz-binding: url("chrome://global/content/platformHTMLBindings.xml#textAreas");
   -moz-appearance: textfield-multiline;
   text-indent: 0;
   -moz-user-select: text;
   text-shadow: none;
+  word-wrap: break-word !important;
 }
 
 textarea > scrollbar {
   cursor: default;
 }
 
 textarea > .anonymous-div,
 input > .anonymous-div {
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -209,16 +209,17 @@ CSS_KEY(block-axis, block_axis)
 CSS_KEY(bold, bold)
 CSS_KEY(bolder, bolder)
 CSS_KEY(border, border)
 CSS_KEY(border-box, border_box)
 CSS_KEY(both, both)
 CSS_KEY(bottom, bottom)
 CSS_KEY(bottom-outside, bottom_outside)
 CSS_KEY(bounding-box, bounding_box)
+CSS_KEY(break-word, break_word)
 CSS_KEY(button, button)
 CSS_KEY(buttonface, buttonface)
 CSS_KEY(buttonhighlight, buttonhighlight)
 CSS_KEY(buttonshadow, buttonshadow)
 CSS_KEY(buttontext, buttontext)
 CSS_KEY(capitalize, capitalize)
 CSS_KEY(caption, caption)
 CSS_KEY(captiontext, captiontext)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -5178,16 +5178,19 @@ PRBool CSSParserImpl::ParseSingleValuePr
   case eCSSProperty_voice_family:
     return ParseFamily(aErrorCode, aValue);
   case eCSSProperty_volume:
     return ParseVariant(aErrorCode, aValue, VARIANT_HPN | VARIANT_KEYWORD,
                         nsCSSProps::kVolumeKTable);
   case eCSSProperty_white_space:
     return ParseVariant(aErrorCode, aValue, VARIANT_HMK,
                         nsCSSProps::kWhitespaceKTable);
+  case eCSSProperty_word_wrap:
+    return ParseVariant(aErrorCode, aValue, VARIANT_HMK,
+                        nsCSSProps::kWordwrapKTable);
   case eCSSProperty_z_index:
     return ParseVariant(aErrorCode, aValue, VARIANT_AHI, nsnull);
   }
   // explicitly do NOT have a default case to let the compiler
   // help find missing properties
   return PR_FALSE;
 }
 
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -509,16 +509,17 @@ CSS_PROP_UIRESET(-moz-user-select, user_
 CSS_PROP_TEXTRESET(vertical-align, vertical_align, VerticalAlign, Text, mVerticalAlign, eCSSType_Value, kVerticalAlignKTable)
 CSS_PROP_VISIBILITY(visibility, visibility, Visibility, Display, mVisibility, eCSSType_Value, kVisibilityKTable)  // reflow for collapse
 CSS_PROP_BACKENDONLY(voice-family, voice_family, VoiceFamily, Aural, mVoiceFamily, eCSSType_Value, nsnull)
 CSS_PROP_BACKENDONLY(volume, volume, Volume, Aural, mVolume, eCSSType_Value, kVolumeKTable)
 CSS_PROP_TEXT(white-space, white_space, WhiteSpace, Text, mWhiteSpace, eCSSType_Value, kWhitespaceKTable)
 CSS_PROP_BACKENDONLY(widows, widows, Widows, Breaks, mWidows, eCSSType_Value, nsnull)
 CSS_PROP_POSITION(width, width, Width, Position, mWidth, eCSSType_Value, kWidthKTable)
 CSS_PROP_TEXT(word-spacing, word_spacing, WordSpacing, Text, mWordSpacing, eCSSType_Value, nsnull)
+CSS_PROP_TEXT(word-wrap, word_wrap, WordWrap, Text, mWordWrap, eCSSType_Value, kWordwrapKTable)
 CSS_PROP_POSITION(z-index, z_index, ZIndex, Position, mZIndex, eCSSType_Value, nsnull)
 
 CSS_PROP_XUL(-moz-box-align, box_align, MozBoxAlign, XUL, mBoxAlign, eCSSType_Value, kBoxAlignKTable) // XXX bug 3935
 CSS_PROP_XUL(-moz-box-direction, box_direction, MozBoxDirection, XUL, mBoxDirection, eCSSType_Value, kBoxDirectionKTable) // XXX bug 3935
 CSS_PROP_XUL(-moz-box-flex, box_flex, MozBoxFlex, XUL, mBoxFlex, eCSSType_Value, nsnull) // XXX bug 3935
 CSS_PROP_XUL(-moz-box-orient, box_orient, MozBoxOrient, XUL, mBoxOrient, eCSSType_Value, kBoxOrientKTable) // XXX bug 3935
 CSS_PROP_XUL(-moz-box-pack, box_pack, MozBoxPack, XUL, mBoxPack, eCSSType_Value, kBoxPackKTable) // XXX bug 3935
 CSS_PROP_XUL(-moz-box-ordinal-group, box_ordinal_group, MozBoxOrdinalGroup, XUL, mBoxOrdinal, eCSSType_Value, nsnull)
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -987,16 +987,22 @@ const PRInt32 nsCSSProps::kWhitespaceKTa
 const PRInt32 nsCSSProps::kWidthKTable[] = {
   eCSSKeyword__moz_max_content, NS_STYLE_WIDTH_MAX_CONTENT,
   eCSSKeyword__moz_min_content, NS_STYLE_WIDTH_MIN_CONTENT,
   eCSSKeyword__moz_fit_content, NS_STYLE_WIDTH_FIT_CONTENT,
   eCSSKeyword__moz_available, NS_STYLE_WIDTH_AVAILABLE,
   eCSSKeyword_UNKNOWN,-1
 };
 
+const PRInt32 nsCSSProps::kWordwrapKTable[] = {
+  eCSSKeyword_normal, NS_STYLE_WORDWRAP_NORMAL,
+  eCSSKeyword_break_word, NS_STYLE_WORDWRAP_BREAK_WORD,
+  eCSSKeyword_UNKNOWN,-1
+};
+
 // Specific keyword tables for XUL.properties
 const PRInt32 nsCSSProps::kBoxAlignKTable[] = {
   eCSSKeyword_stretch,  NS_STYLE_BOX_ALIGN_STRETCH,
   eCSSKeyword_start,   NS_STYLE_BOX_ALIGN_START,
   eCSSKeyword_center, NS_STYLE_BOX_ALIGN_CENTER,
   eCSSKeyword_baseline, NS_STYLE_BOX_ALIGN_BASELINE, 
   eCSSKeyword_end, NS_STYLE_BOX_ALIGN_END, 
   eCSSKeyword_UNKNOWN,-1
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -188,11 +188,12 @@ public:
   static const PRInt32 kUserInputKTable[];
   static const PRInt32 kUserModifyKTable[];
   static const PRInt32 kUserSelectKTable[];
   static const PRInt32 kVerticalAlignKTable[];
   static const PRInt32 kVisibilityKTable[];
   static const PRInt32 kVolumeKTable[];
   static const PRInt32 kWhitespaceKTable[];
   static const PRInt32 kWidthKTable[]; // also min-width, max-width
+  static const PRInt32 kWordwrapKTable[];
 };
 
 #endif /* nsCSSProps_h___ */
--- a/layout/style/nsCSSStruct.h
+++ b/layout/style/nsCSSStruct.h
@@ -260,16 +260,17 @@ struct nsCSSText : public nsCSSStruct  {
   nsCSSValue mTextTransform;
   nsCSSValue mTextAlign;
   nsCSSValue mTextIndent;
   nsCSSValue mDecoration;
   nsCSSValueList* mTextShadow; // NEW
   nsCSSValue mUnicodeBidi;  // NEW
   nsCSSValue mLineHeight;
   nsCSSValue mWhiteSpace;
+  nsCSSValue mWordWrap;
 private:
   nsCSSText(const nsCSSText& aOther); // NOT IMPLEMENTED
 };
 
 struct nsRuleDataText : public nsCSSText {
   nsRuleDataText() {}
 private:
   nsRuleDataText(const nsRuleDataText& aOther); // NOT IMPLEMENTED
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1993,16 +1993,36 @@ nsComputedDOMStyle::GetWhiteSpace(nsIDOM
   } else {
     val->SetIdent(nsGkAtoms::normal);
   }
 
   return CallQueryInterface(val, aValue);
 }
 
 nsresult
+nsComputedDOMStyle::GetWordWrap(nsIDOMCSSValue** aValue)
+{
+  nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
+  NS_ENSURE_TRUE(val, NS_ERROR_OUT_OF_MEMORY);
+
+  const nsStyleText *text = GetStyleText();
+
+  if (text->mWordWrap != NS_STYLE_WORDWRAP_NORMAL) {
+    const nsAFlatCString& wordWrap =
+      nsCSSProps::ValueToKeyword(text->mWordWrap,
+                                 nsCSSProps::kWordwrapKTable);
+    val->SetIdent(wordWrap);
+  } else {
+    val->SetIdent(nsGkAtoms::normal);
+  }
+
+  return CallQueryInterface(val, aValue);
+}
+
+nsresult
 nsComputedDOMStyle::GetVisibility(nsIDOMCSSValue** aValue)
 {
   nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
   NS_ENSURE_TRUE(val, NS_ERROR_OUT_OF_MEMORY);
 
   const nsAFlatCString& value=
     nsCSSProps::ValueToKeyword(GetStyleVisibility()->mVisible,
                                nsCSSProps::kVisibilityKTable);
@@ -4008,17 +4028,18 @@ nsComputedDOMStyle::GetQueryableProperty
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_bottomLeft, OutlineRadiusBottomLeft),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_bottomRight,OutlineRadiusBottomRight),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_topLeft,    OutlineRadiusTopLeft),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_topRight,   OutlineRadiusTopRight),
     COMPUTED_STYLE_MAP_ENTRY(stack_sizing,                  StackSizing),
     COMPUTED_STYLE_MAP_ENTRY(user_focus,                    UserFocus),
     COMPUTED_STYLE_MAP_ENTRY(user_input,                    UserInput),
     COMPUTED_STYLE_MAP_ENTRY(user_modify,                   UserModify),
-    COMPUTED_STYLE_MAP_ENTRY(user_select,                   UserSelect)
+    COMPUTED_STYLE_MAP_ENTRY(user_select,                   UserSelect),
+    COMPUTED_STYLE_MAP_ENTRY(word_wrap,                     WordWrap)
 
 #ifdef MOZ_SVG
     ,
     COMPUTED_STYLE_MAP_ENTRY(clip_path,                     ClipPath),
     COMPUTED_STYLE_MAP_ENTRY(clip_rule,                     ClipRule),
     COMPUTED_STYLE_MAP_ENTRY(color_interpolation,           ColorInterpolation),
     COMPUTED_STYLE_MAP_ENTRY(color_interpolation_filters,   ColorInterpolationFilters),
     COMPUTED_STYLE_MAP_ENTRY(dominant_baseline,             DominantBaseline),
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -244,16 +244,17 @@ private:
   nsresult GetTextAlign(nsIDOMCSSValue** aValue);
   nsresult GetTextDecoration(nsIDOMCSSValue** aValue);
   nsresult GetTextIndent(nsIDOMCSSValue** aValue);
   nsresult GetTextTransform(nsIDOMCSSValue** aValue);
   nsresult GetTextShadow(nsIDOMCSSValue** aValue);
   nsresult GetLetterSpacing(nsIDOMCSSValue** aValue);
   nsresult GetWordSpacing(nsIDOMCSSValue** aValue);
   nsresult GetWhiteSpace(nsIDOMCSSValue** aValue);
+  nsresult GetWordWrap(nsIDOMCSSValue** aValue);
 
   /* Visibility properties */
   nsresult GetOpacity(nsIDOMCSSValue** aValue);
   nsresult GetVisibility(nsIDOMCSSValue** aValue);
 
   /* Direction properties */
   nsresult GetDirection(nsIDOMCSSValue** aValue);
   nsresult GetUnicodeBidi(nsIDOMCSSValue** aValue);
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -2795,16 +2795,29 @@ nsRuleNode::ComputeTextData(void* aStart
     text->mWhiteSpace = parentText->mWhiteSpace;
   }
 
   // word-spacing: normal, length, inherit
   SetCoord(textData.mWordSpacing, text->mWordSpacing, parentText->mWordSpacing,
            SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL,
            aContext, mPresContext, inherited);
 
+  // word-wrap: enum, normal, inherit
+  if (eCSSUnit_Enumerated == textData.mWordWrap.GetUnit()) {
+    text->mWordWrap = textData.mWordWrap.GetIntValue();
+  }
+  else if (eCSSUnit_Normal == textData.mWordWrap.GetUnit() ||
+           eCSSUnit_Initial == textData.mWordWrap.GetUnit()) {
+    text->mWordWrap = NS_STYLE_WORDWRAP_NORMAL;
+  }
+  else if (eCSSUnit_Inherit == textData.mWordWrap.GetUnit()) {
+    inherited = PR_TRUE;
+    text->mWordWrap = parentText->mWordWrap;
+  }
+
   COMPUTE_END_INHERITED(Text, text)
 }
 
 const void*
 nsRuleNode::ComputeTextResetData(void* aStartStruct,
                                  const nsRuleDataStruct& aData, 
                                  nsStyleContext* aContext, 
                                  nsRuleNode* aHighestNode,
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -689,20 +689,21 @@ void nsStyleContext::DumpRegressionData(
   fprintf(out, "%d ", (int)pos->mBoxSizing);
   pos->mZIndex.ToString(str);
   fprintf(out, "%s ", NS_ConvertUTF16toUTF8(str).get());
   fprintf(out, "\" />\n");
 
   // TEXT
   IndentBy(out,aIndent);
   const nsStyleText* text = GetStyleText();
-  fprintf(out, "<text data=\"%d %d %d ",
+  fprintf(out, "<text data=\"%d %d %d %d",
     (int)text->mTextAlign,
     (int)text->mTextTransform,
-    (int)text->mWhiteSpace);
+    (int)text->mWhiteSpace,
+    (int)text->mWordWrap);
   text->mLetterSpacing.ToString(str);
   fprintf(out, "%s ", NS_ConvertUTF16toUTF8(str).get());
   text->mLineHeight.ToString(str);
   fprintf(out, "%s ", NS_ConvertUTF16toUTF8(str).get());
   text->mTextIndent.ToString(str);
   fprintf(out, "%s ", NS_ConvertUTF16toUTF8(str).get());
   text->mWordSpacing.ToString(str);
   fprintf(out, "%s ", NS_ConvertUTF16toUTF8(str).get());
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1638,43 +1638,46 @@ CalcShadowDifference(nsCSSShadowArray* l
 // nsStyleText
 //
 
 nsStyleText::nsStyleText(void)
 { 
   mTextAlign = NS_STYLE_TEXT_ALIGN_DEFAULT;
   mTextTransform = NS_STYLE_TEXT_TRANSFORM_NONE;
   mWhiteSpace = NS_STYLE_WHITESPACE_NORMAL;
+  mWordWrap = NS_STYLE_WORDWRAP_NORMAL;
 
   mLetterSpacing.SetNormalValue();
   mLineHeight.SetNormalValue();
   mTextIndent.SetCoordValue(0);
   mWordSpacing.SetNormalValue();
 
   mTextShadow = nsnull;
 }
 
 nsStyleText::nsStyleText(const nsStyleText& aSource)
   : mTextAlign(aSource.mTextAlign),
     mTextTransform(aSource.mTextTransform),
     mWhiteSpace(aSource.mWhiteSpace),
+    mWordWrap(aSource.mWordWrap),
     mLetterSpacing(aSource.mLetterSpacing),
     mLineHeight(aSource.mLineHeight),
     mTextIndent(aSource.mTextIndent),
     mWordSpacing(aSource.mWordSpacing),
     mTextShadow(aSource.mTextShadow)
 { }
 
 nsStyleText::~nsStyleText(void) { }
 
 nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aOther) const
 {
   if ((mTextAlign != aOther.mTextAlign) ||
       (mTextTransform != aOther.mTextTransform) ||
       (mWhiteSpace != aOther.mWhiteSpace) ||
+      (mWordWrap != aOther.mWordWrap) ||
       (mLetterSpacing != aOther.mLetterSpacing) ||
       (mLineHeight != aOther.mLineHeight) ||
       (mTextIndent != aOther.mTextIndent) ||
       (mWordSpacing != aOther.mWordSpacing))
     return NS_STYLE_HINT_REFLOW;
 
   return CalcShadowDifference(mTextShadow, aOther.mTextShadow);
 }
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -801,16 +801,17 @@ struct nsStyleText {
   nsChangeHint CalcDifference(const nsStyleText& aOther) const;
 #ifdef DEBUG
   static nsChangeHint MaxDifference();
 #endif
 
   PRUint8 mTextAlign;                   // [inherited] see nsStyleConsts.h
   PRUint8 mTextTransform;               // [inherited] see nsStyleConsts.h
   PRUint8 mWhiteSpace;                  // [inherited] see nsStyleConsts.h
+  PRUint8 mWordWrap;                    // [inherited] see nsStyleConsts.h
 
   nsStyleCoord  mLetterSpacing;         // [inherited] 
   nsStyleCoord  mLineHeight;            // [inherited] 
   nsStyleCoord  mTextIndent;            // [inherited] 
   nsStyleCoord  mWordSpacing;           // [inherited] 
 
   nsRefPtr<nsCSSShadowArray> mTextShadow; // [inherited] NULL in case of a zero-length
   
@@ -818,16 +819,20 @@ struct nsStyleText {
     return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
            mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP;
   }
 
   PRBool WhiteSpaceCanWrap() const {
     return mWhiteSpace == NS_STYLE_WHITESPACE_NORMAL ||
            mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP;
   }
+
+  PRBool WordCanWrap() const {
+    return mWordWrap == NS_STYLE_WORDWRAP_BREAK_WORD;
+  }
 };
 
 struct nsStyleVisibility {
   nsStyleVisibility(nsPresContext* aPresContext);
   nsStyleVisibility(const nsStyleVisibility& aVisibility);
   ~nsStyleVisibility() {}
 
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -1662,16 +1662,24 @@ var gCSSProperties = {
 	"word-spacing": {
 		domProp: "wordSpacing",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "normal", "0", "0px", "-0em" ],
 		other_values: [ "1em", "2px", "-3px" ],
 		invalid_values: []
 	},
+	"word-wrap": {
+		domProp: "wordWrap",
+		inherited: true,
+		type: CSS_TYPE_LONGHAND,
+		initial_values: [ "normal" ],
+		other_values: [ "break-word" ],
+		invalid_values: []
+	},
 	"z-index": {
 		domProp: "zIndex",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		/* XXX requires position */
 		initial_values: [ "auto" ],
 		other_values: [ "0", "3", "-7000", "12000" ],
 		invalid_values: [ "3.0", "17.5" ]
--- a/layout/style/viewsource.css
+++ b/layout/style/viewsource.css
@@ -44,16 +44,17 @@
 #viewsource {
   font-family: -moz-fixed;
   font-weight: normal;
   color: black;
   white-space: pre;
 }
 #viewsource.wrap {
   white-space: pre-wrap;
+  word-wrap: break-word;
 }
 pre {
   font: inherit;
   color: inherit;
   white-space: inherit; 
   margin: 0;
 }
 .start-tag {