Bug 1104916 - Implement CSS media query display-mode. r=cam
authorBrendan Dahl <bdahl@mozilla.com>
Tue, 23 Feb 2016 17:10:00 +0100
changeset 285540 9ecc9682c3a35496b18a114b85af871f1d6bffa4
parent 285538 1f797f47e312c4ab9883bd48c9e3501619ff9e95
child 285541 406ee0a64991a8a9bc6b8ef9e1a27a16e80b9b15
push id30031
push userkwierso@gmail.com
push dateThu, 25 Feb 2016 22:25:25 +0000
treeherdermozilla-central@97cf677ee668 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscam
bugs1104916
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1104916 - Implement CSS media query display-mode. r=cam
dom/base/nsGkAtomList.h
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/nsCSSKeywordList.h
layout/style/nsMediaFeatures.cpp
layout/style/nsStyleConsts.h
layout/style/test/chrome/chrome.ini
layout/style/test/chrome/test_display_mode.html
layout/style/test/test_media_queries.html
widget/windows/nsWindow.cpp
xpfe/appshell/nsWebShellWindow.cpp
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -303,16 +303,17 @@ GK_ATOM(digit, "digit")
 GK_ATOM(dir, "dir")
 GK_ATOM(dirAutoSetBy, "dirAutoSetBy")
 GK_ATOM(directionality, "directionality")
 GK_ATOM(directory, "directory")
 GK_ATOM(disableOutputEscaping, "disable-output-escaping")
 GK_ATOM(disabled, "disabled")
 GK_ATOM(disablehistory, "disablehistory")
 GK_ATOM(display, "display")
+GK_ATOM(displayMode, "display-mode")
 GK_ATOM(distinct, "distinct")
 GK_ATOM(div, "div")
 GK_ATOM(dl, "dl")
 GK_ATOM(doctypePublic, "doctype-public")
 GK_ATOM(doctypeSystem, "doctype-system")
 GK_ATOM(document, "document")
 GK_ATOM(download, "download")
 GK_ATOM(DOMAttrModified, "DOMAttrModified")
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -515,28 +515,31 @@ child:
      * content processes always render to a virtual <0, 0> top-left
      * point.
      */
     async Show(ScreenIntSize size,
                ShowInfo info,
                TextureFactoryIdentifier textureFactoryIdentifier,
                uint64_t layersId,
                nullable PRenderFrame renderFrame,
-               bool parentIsActive);
+               bool parentIsActive,
+               nsSizeMode sizeMode);
 
     async LoadURL(nsCString uri, BrowserConfiguration config, ShowInfo info);
 
     async OpenURI(URIParams uri, uint32_t flags);
 
     async CacheFileDescriptor(nsString path, FileDescriptor fd);
 
-    async UpdateDimensions(CSSRect rect, CSSSize size, nsSizeMode sizeMode,
+    async UpdateDimensions(CSSRect rect, CSSSize size,
                            ScreenOrientationInternal orientation,
                            LayoutDeviceIntPoint chromeDisp) compressall;
 
+    async SizeModeChanged(nsSizeMode sizeMode);
+
     /**
      * Sending an activate message moves focus to the child.
      */
     async Activate();
 
     async Deactivate();
 
     async ParentActivated(bool aActivated);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1477,17 +1477,17 @@ TabChild::CancelCachedFileDescriptorCall
 }
 
 void
 TabChild::DoFakeShow(const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                      const uint64_t& aLayersId,
                      PRenderFrameChild* aRenderFrame, const ShowInfo& aShowInfo)
 {
   RecvShow(ScreenIntSize(0, 0), aShowInfo, aTextureFactoryIdentifier,
-           aLayersId, aRenderFrame, mParentIsActive);
+           aLayersId, aRenderFrame, mParentIsActive, nsSizeMode_Normal);
   mDidFakeShow = true;
 }
 
 void
 TabChild::ApplyShowInfo(const ShowInfo& aInfo)
 {
   if (mDidSetRealShowInfo) {
     return;
@@ -1589,20 +1589,22 @@ TabChild::MaybeRequestPreinitCamera()
 #endif
 
 bool
 TabChild::RecvShow(const ScreenIntSize& aSize,
                    const ShowInfo& aInfo,
                    const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                    const uint64_t& aLayersId,
                    PRenderFrameChild* aRenderFrame,
-                   const bool& aParentIsActive)
+                   const bool& aParentIsActive,
+                   const nsSizeMode& aSizeMode)
 {
     MOZ_ASSERT((!mDidFakeShow && aRenderFrame) || (mDidFakeShow && !aRenderFrame));
 
+    mPuppetWidget->SetSizeMode(aSizeMode);
     if (mDidFakeShow) {
         ApplyShowInfo(aInfo);
         RecvParentActivated(aParentIsActive);
         return true;
     }
 
     nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(WebNavigation());
     if (!baseWindow) {
@@ -1628,17 +1630,16 @@ TabChild::RecvShow(const ScreenIntSize& 
     ApplyShowInfo(aInfo);
     RecvParentActivated(aParentIsActive);
 
     return res;
 }
 
 bool
 TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size,
-                               const nsSizeMode& sizeMode,
                                const ScreenOrientationInternal& orientation,
                                const LayoutDeviceIntPoint& chromeDisp)
 {
     if (!mRemoteFrame) {
         return true;
     }
 
     mUnscaledOuterRect = rect;
@@ -1655,25 +1656,40 @@ TabChild::RecvUpdateDimensions(const CSS
 
     // Set the size on the document viewer before we update the widget and
     // trigger a reflow. Otherwise the MobileViewportManager reads the stale
     // size from the content viewer when it computes a new CSS viewport.
     nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
     baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
                                 true);
 
-    mPuppetWidget->SetSizeMode(sizeMode);
     mPuppetWidget->Resize(screenRect.x + chromeDisp.x,
                           screenRect.y + chromeDisp.y,
                           screenSize.width, screenSize.height, true);
 
     return true;
 }
 
 bool
+TabChild::RecvSizeModeChanged(const nsSizeMode& aSizeMode)
+{
+  mPuppetWidget->SetSizeMode(aSizeMode);
+  nsCOMPtr<nsIDocument> document(GetDocument());
+  nsCOMPtr<nsIPresShell> presShell = document->GetShell();
+  if (presShell) {
+    nsPresContext* presContext = presShell->GetPresContext();
+    if (presContext) {
+      presContext->MediaFeatureValuesChangedAllDocuments(eRestyle_Subtree,
+                                                         NS_STYLE_HINT_REFLOW);
+    }
+  }
+  return true;
+}
+
+bool
 TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics)
 {
   return TabChildBase::UpdateFrameHandler(aFrameMetrics);
 }
 
 bool
 TabChild::RecvSuppressDisplayport(const bool& aEnabled)
 {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -321,24 +321,26 @@ public:
                                        override;
 
   virtual bool
   RecvShow(const ScreenIntSize& aSize,
            const ShowInfo& aInfo,
            const TextureFactoryIdentifier& aTextureFactoryIdentifier,
            const uint64_t& aLayersId,
            PRenderFrameChild* aRenderFrame,
-           const bool& aParentIsActive) override;
+           const bool& aParentIsActive,
+           const nsSizeMode& aSizeMode) override;
 
   virtual bool
   RecvUpdateDimensions(const CSSRect& aRect,
                        const CSSSize& aSize,
-                       const nsSizeMode& aSizeMode,
                        const ScreenOrientationInternal& aOrientation,
                        const LayoutDeviceIntPoint& aChromeDisp) override;
+  virtual bool
+  RecvSizeModeChanged(const nsSizeMode& aSizeMode) override;
 
   virtual bool RecvActivate() override;
 
   virtual bool RecvDeactivate() override;
 
   virtual bool RecvMouseEvent(const nsString& aType,
                               const float& aX,
                               const float& aY,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -274,16 +274,17 @@ TabParent::TabParent(nsIContentParent* a
   : TabContext(aContext)
   , mFrameElement(nullptr)
   , mRect(0, 0, 0, 0)
   , mDimensions(0, 0)
   , mOrientation(0)
   , mDPI(0)
   , mDefaultScale(0)
   , mUpdatedDimensions(false)
+  , mSizeMode(nsSizeMode_Normal)
   , mManager(aManager)
   , mDocShellIsActive(false)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
   , mIsDetached(true)
   , mAppPackageFileDescriptorSent(false)
   , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
@@ -889,29 +890,34 @@ TabParent::Show(const ScreenIntSize& siz
                                     &layersId,
                                     &success);
           MOZ_ASSERT(success);
           AddTabParentToTable(layersId, this);
           Unused << SendPRenderFrameConstructor(renderFrame);
         }
     }
 
+    nsCOMPtr<nsISupports> container = mFrameElement->OwnerDoc()->GetContainer();
+    nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
+    nsCOMPtr<nsIWidget> mainWidget;
+    baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+    mSizeMode = mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal;
+
     Unused << SendShow(size, GetShowInfo(), textureFactoryIdentifier,
-                       layersId, renderFrame, aParentIsActive);
+                       layersId, renderFrame, aParentIsActive, mSizeMode);
 }
 
 bool
 TabParent::RecvSetDimensions(const uint32_t& aFlags,
                              const int32_t& aX, const int32_t& aY,
                              const int32_t& aCx, const int32_t& aCy)
 {
   MOZ_ASSERT(!(aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_INNER),
              "We should never see DIM_FLAGS_SIZE_INNER here!");
 
-  nsCOMPtr<nsIWidget> widget = GetWidget();
   NS_ENSURE_TRUE(mFrameElement, true);
   nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell();
   NS_ENSURE_TRUE(docShell, true);
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
   docShell->GetTreeOwner(getter_AddRefs(treeOwner));
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = do_QueryInterface(treeOwner);
   NS_ENSURE_TRUE(treeOwnerAsWin, true);
 
@@ -985,22 +991,30 @@ TabParent::UpdateDimensions(const nsIntR
                                 PixelCastJustification::LayoutDeviceIsScreenForTabDims);
     LayoutDeviceIntSize devicePixelSize =
       ViewAs<LayoutDevicePixel>(mDimensions,
                                 PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 
     CSSRect unscaledRect = devicePixelRect / widgetScale;
     CSSSize unscaledSize = devicePixelSize / widgetScale;
     Unused << SendUpdateDimensions(unscaledRect, unscaledSize,
-                                   widget->SizeMode(),
                                    orientation, chromeOffset);
   }
 }
 
 void
+TabParent::SizeModeChanged(const nsSizeMode& aSizeMode)
+{
+  if (!mIsDestroyed && aSizeMode != mSizeMode) {
+    mSizeMode = aSizeMode;
+    Unused << SendSizeModeChanged(aSizeMode);
+  }
+}
+
+void
 TabParent::UIResolutionChanged()
 {
   if (!mIsDestroyed) {
     // TryCacheDPIAndScale()'s cache is keyed off of
     // mDPI being greater than 0, so this invalidates it.
     mDPI = -1;
     TryCacheDPIAndScale();
     // If mDPI was set to -1 to invalidate it and then TryCacheDPIAndScale
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -332,16 +332,18 @@ public:
 
   // XXX/cjones: it's not clear what we gain by hiding these
   // message-sending functions under a layer of indirection and
   // eating the return values
   void Show(const ScreenIntSize& aSize, bool aParentIsActive);
 
   void UpdateDimensions(const nsIntRect& aRect, const ScreenIntSize& aSize);
 
+  void SizeModeChanged(const nsSizeMode& aSizeMode);
+
   void UIResolutionChanged();
 
   void ThemeChanged();
 
   void HandleAccessKey(nsTArray<uint32_t>& aCharCodes,
                        const bool& aIsTrusted,
                        const int32_t& aModifierMask);
 
@@ -595,16 +597,17 @@ protected:
   ContentCacheInParent mContentCache;
 
   nsIntRect mRect;
   ScreenIntSize mDimensions;
   ScreenOrientationInternal mOrientation;
   float mDPI;
   CSSToLayoutDeviceScale mDefaultScale;
   bool mUpdatedDimensions;
+  nsSizeMode mSizeMode;
   LayoutDeviceIntPoint mChromeOffset;
 
 private:
   void DestroyInternal();
 
   already_AddRefed<nsFrameLoader>
   GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy = false) const;
 
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2046,16 +2046,49 @@ nsPresContext::PostRebuildAllStyleDataEv
 {
   if (!mShell) {
     // We must have been torn down. Nothing to do here.
     return;
   }
   RestyleManager()->PostRebuildAllStyleDataEvent(aExtraHint, aRestyleHint);
 }
 
+struct MediaFeatureHints
+{
+  nsRestyleHint restyleHint;
+  nsChangeHint changeHint;
+};
+
+static bool
+MediaFeatureValuesChangedAllDocumentsCallback(nsIDocument* aDocument, void* aHints)
+{
+  MediaFeatureHints* hints = static_cast<MediaFeatureHints*>(aHints);
+  if (nsIPresShell* shell = aDocument->GetShell()) {
+    if (nsPresContext* pc = shell->GetPresContext()) {
+      pc->MediaFeatureValuesChangedAllDocuments(hints->restyleHint,
+                                                hints->changeHint);
+    }
+  }
+  return true;
+}
+
+void
+nsPresContext::MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint,
+                                                     nsChangeHint aChangeHint)
+{
+    MediaFeatureValuesChanged(aRestyleHint, aChangeHint);
+    MediaFeatureHints hints = {
+      aRestyleHint,
+      aChangeHint
+    };
+
+    mDocument->EnumerateSubDocuments(MediaFeatureValuesChangedAllDocumentsCallback,
+                                     &hints);
+}
+
 void
 nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
                                          nsChangeHint aChangeHint)
 {
   mPendingMediaFeatureValuesChanged = false;
 
   // MediumFeaturesChanged updates the applied rules, so it always gets called.
   if (mShell) {
@@ -2152,16 +2185,35 @@ nsPresContext::HandleMediaFeatureValuesC
 {
   // Null-check mShell in case the shell has been destroyed (and the
   // event is the only thing holding the pres context alive).
   if (mPendingMediaFeatureValuesChanged && mShell) {
     MediaFeatureValuesChanged(nsRestyleHint(0));
   }
 }
 
+static void
+NotifyTabSizeModeChanged(TabParent* aTab, void* aArg)
+{
+  nsSizeMode* sizeMode = static_cast<nsSizeMode*>(aArg);
+  aTab->SizeModeChanged(*sizeMode);
+}
+
+void
+nsPresContext::SizeModeChanged(nsSizeMode aSizeMode)
+{
+  if (HasCachedStyleData()) {
+    nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
+                                            NotifyTabSizeModeChanged,
+                                            &aSizeMode);
+    MediaFeatureValuesChangedAllDocuments(eRestyle_Subtree,
+                                          NS_STYLE_HINT_REFLOW);
+  }
+}
+
 nsCompatibility
 nsPresContext::CompatibilityMode() const
 {
   return Document()->GetCompatibilityMode();
 }
 
 void
 nsPresContext::SetPaginatedScrolling(bool aPaginated)
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -16,16 +16,17 @@
 #include "nsIPresShell.h"
 #include "nsRect.h"
 #include "nsFont.h"
 #include "gfxFontConstants.h"
 #include "nsIAtom.h"
 #include "nsIObserver.h"
 #include "nsITimer.h"
 #include "nsCRT.h"
+#include "nsIWidgetListener.h"
 #include "FramePropertyTable.h"
 #include "nsGkAtoms.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsChangeHint.h"
 #include <algorithm>
 // This also pulls in gfxTypes.h, which we cannot include directly.
 #include "gfxRect.h"
 #include "nsTArray.h"
@@ -274,24 +275,39 @@ public:
    *    rerunning of selector matching
    *
    * For aChangeHint, see RestyleManager::RebuildAllStyleData.  (Passing
    * a nonzero aChangeHint forces rebuilding style data even if
    * nsRestyleHint(0) is passed.)
    */
   void MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
                                  nsChangeHint aChangeHint = nsChangeHint(0));
+  /**
+   * Calls MediaFeatureValuesChanged for this pres context and all descendant
+   * subdocuments that have a pres context. This should be used for media
+   * features that must be updated in all subdocuments e.g. display-mode.
+   */
+  void MediaFeatureValuesChangedAllDocuments(nsRestyleHint aRestyleHint,
+                                             nsChangeHint aChangeHint = nsChangeHint(0));
+
   void PostMediaFeatureValuesChangedEvent();
   void HandleMediaFeatureValuesChangedEvent();
   void FlushPendingMediaFeatureValuesChanged() {
     if (mPendingMediaFeatureValuesChanged)
       MediaFeatureValuesChanged(nsRestyleHint(0));
   }
 
   /**
+   * Updates the size mode on all remote children and recursively notifies this
+   * document and all subdocuments (including remote children) that a media
+   * feature value has changed.
+   */
+  void SizeModeChanged(nsSizeMode aSizeMode);
+
+  /**
    * Access compatibility mode for this context.  This is the same as
    * our document's compatibility mode.
    */
   nsCompatibility CompatibilityMode() const;
 
   /**
    * Notify the context that the document's compatibility mode has changed
    */
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -164,16 +164,17 @@ CSS_KEY(bold-script, bold_script)
 CSS_KEY(bolder, bolder)
 CSS_KEY(border-box, border_box)
 CSS_KEY(both, both)
 CSS_KEY(bottom, bottom)
 CSS_KEY(bottom-outside, bottom_outside)
 CSS_KEY(break-all, break_all)
 CSS_KEY(break-word, break_word)
 CSS_KEY(brightness, brightness)
+CSS_KEY(browser, browser)
 CSS_KEY(bullets, bullets)
 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)
@@ -275,16 +276,17 @@ CSS_KEY(flat, flat)
 CSS_KEY(flex, flex)
 CSS_KEY(flex-end, flex_end)
 CSS_KEY(flex-start, flex_start)
 CSS_KEY(flip, flip)
 CSS_KEY(forwards, forwards)
 CSS_KEY(fraktur, fraktur)
 CSS_KEY(from-image, from_image)
 CSS_KEY(full-width, full_width)
+CSS_KEY(fullscreen, fullscreen)
 CSS_KEY(grab, grab)
 CSS_KEY(grabbing, grabbing)
 CSS_KEY(grad, grad)
 CSS_KEY(grayscale, grayscale)
 CSS_KEY(graytext, graytext)
 CSS_KEY(grid, grid)
 CSS_KEY(groove, groove)
 CSS_KEY(hard-light, hard_light)
@@ -530,16 +532,17 @@ CSS_KEY(space-around, space_around)
 CSS_KEY(space-between, space_between)
 CSS_KEY(space-evenly, space_evenly)
 CSS_KEY(span, span)
 CSS_KEY(spell-out, spell_out)
 CSS_KEY(square, square)
 CSS_KEY(stacked-fractions, stacked_fractions)
 CSS_KEY(start, start)
 CSS_KEY(static, static)
+CSS_KEY(standalone, standalone)
 CSS_KEY(status-bar, status_bar)
 CSS_KEY(step-end, step_end)
 CSS_KEY(step-start, step_start)
 CSS_KEY(sticky, sticky)
 CSS_KEY(stretch, stretch)
 CSS_KEY(stretch-to-fit, stretch_to_fit)
 CSS_KEY(stretched, stretched)
 CSS_KEY(strict, strict)
@@ -701,16 +704,17 @@ CSS_KEY(menuarrow, menuarrow)
 CSS_KEY(menuimage, menuimage)
 CSS_KEY(menuitemtext, menuitemtext)
 CSS_KEY(menulist, menulist)
 CSS_KEY(menulist-button, menulist_button)
 CSS_KEY(menulist-text, menulist_text)
 CSS_KEY(menulist-textfield, menulist_textfield)
 CSS_KEY(meterbar, meterbar)
 CSS_KEY(meterchunk, meterchunk)
+CSS_KEY(minimal-ui, minimal_ui)
 CSS_KEY(range, range)
 CSS_KEY(range-thumb, range_thumb)
 CSS_KEY(sans-serif, sans_serif)
 CSS_KEY(sans-serif-bold-italic, sans_serif_bold_italic)
 CSS_KEY(sans-serif-italic, sans_serif_italic)
 CSS_KEY(scale-horizontal, scale_horizontal)
 CSS_KEY(scale-vertical, scale_vertical)
 CSS_KEY(scalethumb-horizontal, scalethumb_horizontal)
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -12,16 +12,17 @@
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsCSSValue.h"
 #ifdef XP_WIN
 #include "mozilla/LookAndFeel.h"
 #endif
 #include "nsCSSRuleProcessor.h"
 #include "nsDeviceContext.h"
+#include "nsIBaseWindow.h"
 #include "nsIDocument.h"
 #include "nsContentUtils.h"
 #include "mozilla/StyleSheetHandle.h"
 #include "mozilla/StyleSheetHandleInlines.h"
 
 using namespace mozilla;
 
 static const nsCSSProps::KTableEntry kOrientationKeywords[] = {
@@ -31,16 +32,24 @@ static const nsCSSProps::KTableEntry kOr
 };
 
 static const nsCSSProps::KTableEntry kScanKeywords[] = {
   { eCSSKeyword_progressive,              NS_STYLE_SCAN_PROGRESSIVE },
   { eCSSKeyword_interlace,                NS_STYLE_SCAN_INTERLACE },
   { eCSSKeyword_UNKNOWN,                  -1 }
 };
 
+static const nsCSSProps::KTableEntry kDisplayModeKeywords[] = {
+  { eCSSKeyword_browser,                 NS_STYLE_DISPLAY_MODE_BROWSER },
+  { eCSSKeyword_minimal_ui,              NS_STYLE_DISPLAY_MODE_MINIMAL_UI },
+  { eCSSKeyword_standalone,              NS_STYLE_DISPLAY_MODE_STANDALONE },
+  { eCSSKeyword_fullscreen,              NS_STYLE_DISPLAY_MODE_FULLSCREEN },
+  { eCSSKeyword_UNKNOWN,                 -1 }
+};
+
 #ifdef XP_WIN
 struct WindowsThemeName {
   LookAndFeel::WindowsTheme id;
   const wchar_t* name;
 };
 
 // Windows theme identities used in the -moz-windows-theme media query.
 const WindowsThemeName themeStrings[] = {
@@ -300,16 +309,44 @@ GetScan(nsPresContext* aPresContext, con
 {
   // Since Gecko doesn't support the 'tv' media type, the 'scan'
   // feature is never present.
   aResult.Reset();
   return NS_OK;
 }
 
 static nsresult
+GetDisplayMode(nsPresContext* aPresContext, const nsMediaFeature*,
+               nsCSSValue& aResult)
+{
+  nsCOMPtr<nsISupports> container = aPresContext->GetRootPresContext()->
+    Document()->GetContainer();
+  nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
+  if (!baseWindow) {
+    aResult.SetIntValue(NS_STYLE_DISPLAY_MODE_BROWSER, eCSSUnit_Enumerated);
+    return NS_OK;
+  }
+  nsCOMPtr<nsIWidget> mainWidget;
+  baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+  int32_t displayMode;
+  nsSizeMode mode = mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal;
+  switch (mode) {
+    case nsSizeMode_Fullscreen:
+      displayMode = NS_STYLE_DISPLAY_MODE_FULLSCREEN;
+      break;
+    default:
+      displayMode = NS_STYLE_DISPLAY_MODE_BROWSER;
+      break;
+  }
+
+  aResult.SetIntValue(displayMode, eCSSUnit_Enumerated);
+  return NS_OK;
+}
+
+static nsresult
 GetGrid(nsPresContext* aPresContext, const nsMediaFeature*,
         nsCSSValue& aResult)
 {
   // Gecko doesn't support grid devices (e.g., ttys), so the 'grid'
   // feature is always 0.
   aResult.SetIntValue(0, eCSSUnit_Integer);
   return NS_OK;
 }
@@ -528,16 +565,24 @@ nsMediaFeatures::features[] = {
   {
     &nsGkAtoms::grid,
     nsMediaFeature::eMinMaxNotAllowed,
     nsMediaFeature::eBoolInteger,
     nsMediaFeature::eNoRequirements,
     { nullptr },
     GetGrid
   },
+  {
+    &nsGkAtoms::displayMode,
+    nsMediaFeature::eMinMaxNotAllowed,
+    nsMediaFeature::eEnumerated,
+    nsMediaFeature::eNoRequirements,
+    { kDisplayModeKeywords },
+    GetDisplayMode
+  },
 
   // Webkit extensions that we support for de-facto web compatibility
   // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
   {
     &nsGkAtoms::devicePixelRatio,
     nsMediaFeature::eMinMaxAllowed,
     nsMediaFeature::eFloat,
     nsMediaFeature::eHasWebkitPrefix |
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -1200,11 +1200,17 @@ enum class FillMode : uint32_t;
 // orientation
 #define NS_STYLE_ORIENTATION_PORTRAIT           0
 #define NS_STYLE_ORIENTATION_LANDSCAPE          1
 
 // scan
 #define NS_STYLE_SCAN_PROGRESSIVE               0
 #define NS_STYLE_SCAN_INTERLACE                 1
 
+// display-mode
+#define NS_STYLE_DISPLAY_MODE_BROWSER           0
+#define NS_STYLE_DISPLAY_MODE_MINIMAL_UI        1
+#define NS_STYLE_DISPLAY_MODE_STANDALONE        2
+#define NS_STYLE_DISPLAY_MODE_FULLSCREEN        3
+
 } // namespace mozilla
 
 #endif /* nsStyleConsts_h___ */
--- a/layout/style/test/chrome/chrome.ini
+++ b/layout/style/test/chrome/chrome.ini
@@ -11,11 +11,12 @@ support-files =
 
 [test_addSheet.html]
 [test_additional_sheets.html]
 [test_author_specified_style.html]
 [test_bug418986-2.xul]
 [test_bug1157097.html]
 [test_bug1160724.xul]
 [test_bug535806.xul]
+[test_display_mode.html]
 [test_hover.html]
 skip-if = buildapp == 'mulet'
 [test_moz_document_rules.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1104916
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Display Mode</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+/** Test for Display Mode **/
+SimpleTest.waitForExplicitFinish();
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function waitOneEvent(element, name) {
+  return new Promise(function(resolve, reject) {
+    element.addEventListener(name, function listener() {
+      element.removeEventListener(name, listener);
+      resolve();
+    });
+  });
+}
+
+add_task(function* () {
+  yield waitOneEvent(window, "load");
+
+  var iframe = document.getElementById("subdoc");
+  var subdoc = iframe.contentDocument;
+  var style = subdoc.getElementById("style");
+  var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body, "");
+  var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+  function queryApplies(q) {
+    style.setAttribute("media", q);
+    return bodyComputedStyled.getPropertyValue("text-decoration") == "underline";
+  }
+
+  function shouldApply(q) {
+    ok(queryApplies(q), q + " should apply");
+  }
+
+  function shouldNotApply(q) {
+    ok(!queryApplies(q), q + " should not apply");
+  }
+
+  shouldApply("all and (display-mode: browser)");
+  shouldNotApply("all and (display-mode: fullscreen)");
+  shouldNotApply("all and (display-mode: standalone)");
+  shouldNotApply("all and (display-mode: minimal-ui)");
+
+  // Test entering the OS's fullscreen mode.
+  var fullScreenEntered = waitOneEvent(win, "sizemodechange");
+  synthesizeKey("VK_F11", {});
+  yield fullScreenEntered;
+  shouldApply("all and (display-mode: fullscreen)");
+  shouldNotApply("all and (display-mode: browser)");
+  var fullScreenExited = waitOneEvent(win, "sizemodechange");
+  synthesizeKey("VK_F11", {});
+  yield fullScreenExited;
+  shouldNotApply("all and (display-mode: fullscreen)");
+  shouldApply("all and (display-mode: browser)");
+
+  // Test entering fullscreen through document requestFullScreen.
+  fullScreenEntered = waitOneEvent(document, "mozfullscreenchange");
+  document.body.mozRequestFullScreen();
+  yield fullScreenEntered
+  ok(document.mozFullScreenElement, "window entered fullscreen");
+  shouldApply("all and (display-mode: fullscreen)");
+  shouldNotApply("all and (display-mode: browser)");
+  fullScreenExited = waitOneEvent(document, "mozfullscreenchange");
+  document.mozCancelFullScreen();
+  yield fullScreenExited;
+  ok(!document.mozFullScreenElement, "window exited fullscreen");
+  shouldNotApply("all and (display-mode: fullscreen)");
+  shouldApply("all and (display-mode: browser)");
+});
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a>
+<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/media_queries_iframe.html"></iframe>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -248,16 +248,24 @@ function run() {
     expression_should_not_be_parseable("min-" + feature + ": -1px");
     expression_should_not_be_parseable("max-" + feature + ": -1px");
     expression_should_not_be_parseable(feature + ": -0.00001mm");
     expression_should_not_be_parseable(feature + ": -100000em");
     expression_should_not_be_parseable("min-" + feature);
     expression_should_not_be_parseable("max-" + feature);
   }
 
+  var mediatypes = ["browser", "minimal-ui", "standalone", "fullscreen"];
+
+  mediatypes.forEach(function(type) {
+    expression_should_be_parseable("display-mode: " + type);
+  });
+
+  expression_should_not_be_parseable("display-mode: invalid")
+
   var content_div = document.getElementById("content");
   content_div.style.font = "initial";
   var em_size =
     getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
 
   // in this test, assume the common underlying implementation is correct
   var width_val = 117; // pick two not-too-round numbers
   var height_val = 76;
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -6154,16 +6154,18 @@ void nsWindow::OnWindowPosChanged(WINDOW
     // handle it.
     if (mSizeMode == nsSizeMode_Minimized && (wp->flags & SWP_NOACTIVATE))
       return;
 
     WINDOWPLACEMENT pl;
     pl.length = sizeof(pl);
     ::GetWindowPlacement(mWnd, &pl);
 
+    nsSizeMode previousSizeMode = mSizeMode;
+
     // Windows has just changed the size mode of this window. The call to
     // SizeModeChanged will trigger a call into SetSizeMode where we will
     // set the min/max window state again or for nsSizeMode_Normal, call
     // SetWindow with a parameter of SW_RESTORE. There's no need however as
     // this window's mode has already changed. Updating mSizeMode here
     // insures the SetSizeMode call is a no-op. Addresses a bug on Win7 related
     // to window docking. (bug 489258)
     if (pl.showCmd == SW_SHOWMAXIMIZED)
@@ -6198,17 +6200,17 @@ void nsWindow::OnWindowPosChanged(WINDOW
                  ("*** mSizeMode: nsSizeMode_Maximized\n"));
         break;
       default:
           MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** mSizeMode: ??????\n"));
         break;
     }
 #endif
 
-    if (mWidgetListener)
+    if (mWidgetListener && mSizeMode != previousSizeMode)
       mWidgetListener->SizeModeChanged(mSizeMode);
 
     // If window was restored, window activation was bypassed during the 
     // SetSizeMode call originating from OnWindowPosChanging to avoid saving
     // pre-restore attributes. Force activation now to get correct attributes.
     if (mLastSizeMode != nsSizeMode_Normal && mSizeMode == nsSizeMode_Normal)
       DispatchFocusToTopLevelWindow(true);
 
--- a/xpfe/appshell/nsWebShellWindow.cpp
+++ b/xpfe/appshell/nsWebShellWindow.cpp
@@ -360,16 +360,21 @@ nsWebShellWindow::SizeModeChanged(nsSize
         ourWindow->SetFullScreen(false);
       }
     }
 
     // And always fire a user-defined sizemodechange event on the window
     ourWindow->DispatchCustomEvent(NS_LITERAL_STRING("sizemodechange"));
   }
 
+  nsIPresShell* presShell;
+  if ((presShell = GetPresShell())) {
+    presShell->GetPresContext()->SizeModeChanged(sizeMode);
+  }
+
   // Note the current implementation of SetSizeMode just stores
   // the new state; it doesn't actually resize. So here we store
   // the state and pass the event on to the OS. The day is coming
   // when we'll handle the event here, and the return result will
   // then need to be different.
 }
 
 void