summaryrefslogtreecommitdiffstats
path: root/layout/style
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style')
-rw-r--r--layout/style/FontFaceSet.cpp12
-rw-r--r--layout/style/nsLayoutStylesheetCache.cpp293
-rw-r--r--layout/style/nsStyleStruct.cpp23
-rw-r--r--layout/style/nsStyleStruct.h11
-rw-r--r--layout/style/res/forms.css5
-rw-r--r--layout/style/res/html.css5
-rw-r--r--layout/style/test/mochitest.ini1
-rw-r--r--layout/style/test/test_dynamic_change_causing_reflow.html107
-rw-r--r--layout/style/test/test_viewport_scrollbar_causing_reflow.html125
9 files changed, 252 insertions, 330 deletions
diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp
index 59626fba4..550a7d71a 100644
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -343,17 +343,7 @@ FontFaceSet::Load(JSContext* aCx,
}
}
- nsIGlobalObject* globalObject = GetParentObject();
- if (!globalObject) {
- aRv.Throw(NS_ERROR_FAILURE);
- return nullptr;
- }
-
- JS::Rooted<JSObject*> jsGlobal(aCx, globalObject->GetGlobalJSObject());
- GlobalObject global(aCx, jsGlobal);
-
- RefPtr<Promise> result = Promise::All(global, promises, aRv);
- return result.forget();
+ return Promise::All(aCx, promises, aRv);
}
bool
diff --git a/layout/style/nsLayoutStylesheetCache.cpp b/layout/style/nsLayoutStylesheetCache.cpp
index f8aea5541..e8c6d09d7 100644
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -23,19 +23,6 @@
#include "nsPrintfCString.h"
#include "nsXULAppAPI.h"
-// Includes for the crash report annotation in ErrorLoadingSheet.
-#ifdef MOZ_CRASHREPORTER
-#include "mozilla/Omnijar.h"
-#include "nsDirectoryService.h"
-#include "nsDirectoryServiceDefs.h"
-#include "nsExceptionHandler.h"
-#include "nsIChromeRegistry.h"
-#include "nsISimpleEnumerator.h"
-#include "nsISubstitutingProtocolHandler.h"
-#include "zlib.h"
-#include "nsZipArchive.h"
-#endif
-
using namespace mozilla;
using namespace mozilla::css;
@@ -463,280 +450,6 @@ nsLayoutStylesheetCache::LoadSheetFile(nsIFile* aFile,
LoadSheet(uri, aSheet, aParsingMode, aFailureAction);
}
-#ifdef MOZ_CRASHREPORTER
-static inline nsresult
-ComputeCRC32(nsIFile* aFile, uint32_t* aResult)
-{
- PRFileDesc* fd;
- nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
- NS_ENSURE_SUCCESS(rv, rv);
-
- uint32_t crc = crc32(0, nullptr, 0);
-
- unsigned char buf[512];
- int32_t n;
- while ((n = PR_Read(fd, buf, sizeof(buf))) > 0) {
- crc = crc32(crc, buf, n);
- }
- PR_Close(fd);
-
- if (n < 0) {
- return NS_ERROR_FAILURE;
- }
-
- *aResult = crc;
- return NS_OK;
-}
-
-static void
-ListInterestingFiles(nsString& aAnnotation, nsIFile* aFile,
- const nsTArray<nsString>& aInterestingFilenames)
-{
- nsString filename;
- aFile->GetLeafName(filename);
- for (const nsString& interestingFilename : aInterestingFilenames) {
- if (interestingFilename == filename) {
- nsString path;
- aFile->GetPath(path);
- aAnnotation.AppendLiteral(" ");
- aAnnotation.Append(path);
- aAnnotation.AppendLiteral(" (");
- int64_t size;
- if (NS_SUCCEEDED(aFile->GetFileSize(&size))) {
- aAnnotation.AppendPrintf("%ld", size);
- } else {
- aAnnotation.AppendLiteral("???");
- }
- aAnnotation.AppendLiteral(" bytes, crc32 = ");
- uint32_t crc;
- nsresult rv = ComputeCRC32(aFile, &crc);
- if (NS_SUCCEEDED(rv)) {
- aAnnotation.AppendPrintf("0x%08x)\n", crc);
- } else {
- aAnnotation.AppendPrintf("error 0x%08x)\n", uint32_t(rv));
- }
- return;
- }
- }
-
- bool isDir = false;
- aFile->IsDirectory(&isDir);
-
- if (!isDir) {
- return;
- }
-
- nsCOMPtr<nsISimpleEnumerator> entries;
- if (NS_FAILED(aFile->GetDirectoryEntries(getter_AddRefs(entries)))) {
- aAnnotation.AppendLiteral(" (failed to enumerated directory)\n");
- return;
- }
-
- for (;;) {
- bool hasMore = false;
- if (NS_FAILED(entries->HasMoreElements(&hasMore))) {
- aAnnotation.AppendLiteral(" (failed during directory enumeration)\n");
- return;
- }
- if (!hasMore) {
- break;
- }
-
- nsCOMPtr<nsISupports> entry;
- if (NS_FAILED(entries->GetNext(getter_AddRefs(entry)))) {
- aAnnotation.AppendLiteral(" (failed during directory enumeration)\n");
- return;
- }
-
- nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
- if (file) {
- ListInterestingFiles(aAnnotation, file, aInterestingFilenames);
- }
- }
-}
-
-// Generate a crash report annotation to help debug issues with style
-// sheets failing to load (bug 1194856).
-static void
-AnnotateCrashReport(nsIURI* aURI)
-{
- nsAutoCString spec;
- nsAutoCString scheme;
- nsDependentCSubstring filename;
- if (aURI) {
- spec = aURI->GetSpecOrDefault();
- aURI->GetScheme(scheme);
- int32_t i = spec.RFindChar('/');
- if (i != -1) {
- filename.Rebind(spec, i + 1);
- }
- }
-
- nsString annotation;
-
- // The URL of the sheet that failed to load.
- annotation.AppendLiteral("Error loading sheet: ");
- annotation.Append(NS_ConvertUTF8toUTF16(spec).get());
- annotation.Append('\n');
-
- annotation.AppendLiteral("NS_ERROR_FILE_CORRUPTION reason: ");
- if (nsZipArchive::sFileCorruptedReason) {
- annotation.Append(NS_ConvertUTF8toUTF16(nsZipArchive::sFileCorruptedReason).get());
- annotation.Append('\n');
- } else {
- annotation.AppendLiteral("(none)\n");
- }
-
- // The jar: or file: URL that the sheet's resource: or chrome: URL
- // resolves to.
- if (scheme.EqualsLiteral("resource")) {
- annotation.AppendLiteral("Real location: ");
- nsCOMPtr<nsISubstitutingProtocolHandler> handler;
- nsCOMPtr<nsIIOService> io(do_GetIOService());
- if (io) {
- nsCOMPtr<nsIProtocolHandler> ph;
- io->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
- if (ph) {
- handler = do_QueryInterface(ph);
- }
- }
- if (!handler) {
- annotation.AppendLiteral("(ResolveURI failed)\n");
- } else {
- nsAutoCString resolvedSpec;
- handler->ResolveURI(aURI, resolvedSpec);
- annotation.Append(NS_ConvertUTF8toUTF16(resolvedSpec));
- annotation.Append('\n');
- }
- } else if (scheme.EqualsLiteral("chrome")) {
- annotation.AppendLiteral("Real location: ");
- nsCOMPtr<nsIChromeRegistry> reg =
- mozilla::services::GetChromeRegistryService();
- if (!reg) {
- annotation.AppendLiteral("(no chrome registry)\n");
- } else {
- nsCOMPtr<nsIURI> resolvedURI;
- reg->ConvertChromeURL(aURI, getter_AddRefs(resolvedURI));
- if (!resolvedURI) {
- annotation.AppendLiteral("(ConvertChromeURL failed)\n");
- } else {
- annotation.Append(
- NS_ConvertUTF8toUTF16(resolvedURI->GetSpecOrDefault()));
- annotation.Append('\n');
- }
- }
- }
-
- nsTArray<nsString> interestingFiles;
- interestingFiles.AppendElement(NS_LITERAL_STRING("chrome.manifest"));
- interestingFiles.AppendElement(NS_LITERAL_STRING("omni.ja"));
- interestingFiles.AppendElement(NS_ConvertUTF8toUTF16(filename));
-
- annotation.AppendLiteral("GRE directory: ");
- nsCOMPtr<nsIFile> file;
- nsDirectoryService::gService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
- getter_AddRefs(file));
- if (file) {
- // The Firefox installation directory.
- nsString path;
- file->GetPath(path);
- annotation.Append(path);
- annotation.Append('\n');
-
- // List interesting files -- any chrome.manifest or omni.ja file or any file
- // whose name is the sheet's filename -- under the Firefox installation
- // directory.
- annotation.AppendLiteral("Interesting files in the GRE directory:\n");
- ListInterestingFiles(annotation, file, interestingFiles);
-
- // If the Firefox installation directory has a chrome.manifest file, let's
- // see what's in it.
- file->Append(NS_LITERAL_STRING("chrome.manifest"));
- bool exists = false;
- file->Exists(&exists);
- if (exists) {
- annotation.AppendLiteral("Contents of chrome.manifest:\n[[[\n");
- PRFileDesc* fd;
- if (NS_SUCCEEDED(file->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
- nsCString contents;
- char buf[512];
- int32_t n;
- while ((n = PR_Read(fd, buf, sizeof(buf))) > 0) {
- contents.Append(buf, n);
- }
- if (n < 0) {
- annotation.AppendLiteral(" (error while reading)\n");
- } else {
- annotation.Append(NS_ConvertUTF8toUTF16(contents));
- }
- PR_Close(fd);
- }
- annotation.AppendLiteral("]]]\n");
- }
- } else {
- annotation.AppendLiteral("(none)\n");
- }
-
- // The jar: or file: URL prefix that chrome: and resource: URLs get translated
- // to.
- annotation.AppendLiteral("GRE omnijar URI string: ");
- nsCString uri;
- nsresult rv = Omnijar::GetURIString(Omnijar::GRE, uri);
- if (NS_FAILED(rv)) {
- annotation.AppendLiteral("(failed)\n");
- } else {
- annotation.Append(NS_ConvertUTF8toUTF16(uri));
- annotation.Append('\n');
- }
-
- RefPtr<nsZipArchive> zip = Omnijar::GetReader(Omnijar::GRE);
- if (zip) {
- // List interesting files in the GRE omnijar.
- annotation.AppendLiteral("Interesting files in the GRE omnijar:\n");
- nsZipFind* find;
- rv = zip->FindInit(nullptr, &find);
- if (NS_FAILED(rv)) {
- annotation.AppendPrintf(" (FindInit failed with 0x%08x)\n", rv);
- } else if (!find) {
- annotation.AppendLiteral(" (FindInit returned null)\n");
- } else {
- const char* result;
- uint16_t len;
- while (NS_SUCCEEDED(find->FindNext(&result, &len))) {
- nsCString itemPathname;
- nsString itemFilename;
- itemPathname.Append(result, len);
- int32_t i = itemPathname.RFindChar('/');
- if (i != -1) {
- itemFilename = NS_ConvertUTF8toUTF16(Substring(itemPathname, i + 1));
- }
- for (const nsString& interestingFile : interestingFiles) {
- if (interestingFile == itemFilename) {
- annotation.AppendLiteral(" ");
- annotation.Append(NS_ConvertUTF8toUTF16(itemPathname));
- nsZipItem* item = zip->GetItem(itemPathname.get());
- if (!item) {
- annotation.AppendLiteral(" (GetItem failed)\n");
- } else {
- annotation.AppendPrintf(" (%d bytes, crc32 = 0x%08x)\n",
- item->RealSize(),
- item->CRC32());
- }
- break;
- }
- }
- }
- delete find;
- }
- } else {
- annotation.AppendLiteral("No GRE omnijar\n");
- }
-
- CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("SheetLoadFailure"),
- NS_ConvertUTF16toUTF8(annotation));
-}
-#endif
-
static void
ErrorLoadingSheet(nsIURI* aURI, const char* aMsg, FailureAction aFailureAction)
{
@@ -751,9 +464,6 @@ ErrorLoadingSheet(nsIURI* aURI, const char* aMsg, FailureAction aFailureAction)
}
}
-#ifdef MOZ_CRASHREPORTER
- AnnotateCrashReport(aURI);
-#endif
NS_RUNTIMEABORT(errorMessage.get());
}
@@ -780,9 +490,6 @@ nsLayoutStylesheetCache::LoadSheet(nsIURI* aURI,
}
}
-#ifdef MOZ_CRASHREPORTER
- nsZipArchive::sFileCorruptedReason = nullptr;
-#endif
nsresult rv = loader->LoadSheetSync(aURI, aParsingMode, true, aSheet);
if (NS_FAILED(rv)) {
ErrorLoadingSheet(aURI,
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
index 2f12d6201..553239e0e 100644
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1629,23 +1629,11 @@ nsStylePosition::CalcDifference(const nsStylePosition& aNewData,
if (aOldStyleVisibility) {
bool isVertical = WritingMode(aOldStyleVisibility).IsVertical();
if (isVertical ? widthChanged : heightChanged) {
- // Block-size changes can affect descendant intrinsic sizes due to
- // replaced elements with percentage bsizes in descendants which
- // also have percentage bsizes. This is handled via
- // nsChangeHint_UpdateComputedBSize which clears intrinsic sizes
- // for frames that have such replaced elements.
- hint |= nsChangeHint_NeedReflow |
- nsChangeHint_UpdateComputedBSize |
- nsChangeHint_ReflowChangesSizeOrPosition;
+ hint |= nsChangeHint_ReflowHintsForBSizeChange;
}
if (isVertical ? heightChanged : widthChanged) {
- // None of our inline-size differences can affect descendant
- // intrinsic sizes and none of them need to force children to
- // reflow.
- hint |= nsChangeHint_AllReflowHints &
- ~(nsChangeHint_ClearDescendantIntrinsics |
- nsChangeHint_NeedDirtyReflow);
+ hint |= nsChangeHint_ReflowHintsForISizeChange;
}
} else {
if (widthChanged || heightChanged) {
@@ -3258,8 +3246,6 @@ nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
|| mDisplay != aNewData.mDisplay
|| mContain != aNewData.mContain
|| (mFloat == StyleFloat::None) != (aNewData.mFloat == StyleFloat::None)
- || mOverflowX != aNewData.mOverflowX
- || mOverflowY != aNewData.mOverflowY
|| mScrollBehavior != aNewData.mScrollBehavior
|| mScrollSnapTypeX != aNewData.mScrollSnapTypeX
|| mScrollSnapTypeY != aNewData.mScrollSnapTypeY
@@ -3271,6 +3257,11 @@ nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
hint |= nsChangeHint_ReconstructFrame;
}
+ if (mOverflowX != aNewData.mOverflowX
+ || mOverflowY != aNewData.mOverflowY) {
+ hint |= nsChangeHint_CSSOverflowChange;
+ }
+
/* Note: When mScrollBehavior, mScrollSnapTypeX, mScrollSnapTypeY,
* mScrollSnapPointsX, mScrollSnapPointsY, or mScrollSnapDestination are
* changed, nsChangeHint_NeutralChange is not sufficient to enter
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
index ca5d03056..05a6be91e 100644
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1197,11 +1197,14 @@ private:
nsCSSShadowItem mArray[1]; // This MUST be the last item
};
-// Border widths are rounded to the nearest-below integer number of pixels,
-// but values between zero and one device pixels are always rounded up to
-// one device pixel.
+// Border widths are rounded to the nearest integer number of pixels, but values
+// between zero and one device pixels are always rounded up to one device pixel.
#define NS_ROUND_BORDER_TO_PIXELS(l,tpp) \
- ((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp))
+ ((l) == 0) ? 0 : std::max((tpp), ((l) + ((tpp) / 2)) / (tpp) * (tpp))
+// Caret widths are rounded to the nearest-below integer number of pixels, but values
+// between zero and one device pixels are always rounded up to one device pixel.
+#define NS_ROUND_CARET_TO_PIXELS(l,tpp) \
+ ((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp))
// Outline offset is rounded to the nearest integer number of pixels, but values
// between zero and one device pixels are always rounded up to one device pixel.
// Note that the offset can be negative.
diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css
index f045540b1..e7566e183 100644
--- a/layout/style/res/forms.css
+++ b/layout/style/res/forms.css
@@ -1135,3 +1135,8 @@ input[type="number"] > div > div > div:hover {
/* give some indication of hover state for the up/down buttons */
background-color: lightblue;
}
+
+input[type="date"],
+input[type="time"] {
+ overflow: hidden !important;
+}
diff --git a/layout/style/res/html.css b/layout/style/res/html.css
index a779461de..bc3f08210 100644
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -774,6 +774,11 @@ input[type="time"] > xul|datetimebox {
-moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
}
+input[type="date"] > xul|datetimebox {
+ display: flex;
+ -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#date-input");
+}
+
/* details & summary */
/* Need to revert Bug 1259889 Part 2 when removing details preference. */
@supports -moz-bool-pref("dom.details_element.enabled") {
diff --git a/layout/style/test/mochitest.ini b/layout/style/test/mochitest.ini
index 406c6f901..8182691ca 100644
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -295,6 +295,7 @@ skip-if = toolkit == 'android'
[test_variables.html]
support-files = support/external-variable-url.css
[test_video_object_fit.html]
+[test_viewport_scrollbar_causing_reflow.html]
[test_viewport_units.html]
[test_visited_image_loading.html]
skip-if = toolkit == 'android' #TIMED_OUT
diff --git a/layout/style/test/test_dynamic_change_causing_reflow.html b/layout/style/test/test_dynamic_change_causing_reflow.html
index a941191f6..a5bb3045c 100644
--- a/layout/style/test/test_dynamic_change_causing_reflow.html
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -95,6 +95,90 @@ const gTestcases = [
expectReflow: true,
},
+ // * Changing 'overflow' on <body> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: visible",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <html> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Setting 'overflow' on arbitrary node should cause reflow as well as
+ // frame reconstruction
+ {
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: visible",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
// * Changing 'display' should cause frame construction and reflow.
{
beforeStyle: "display: inline",
@@ -135,23 +219,34 @@ function runOneTest(aTestcase)
return;
}
+ // Figure out which element we'll be tweaking (defaulting to gElem)
+ let elem = aTestcase.elem ?
+ aTestcase.elem : gElem;
+
+ // Verify that 'style' attribute is unset (avoid causing ourselves trouble):
+ if (elem.hasAttribute("style")) {
+ ok(false,
+ "test element has 'style' attribute already set! We're going to stomp " +
+ "on whatever's there when we clean up...");
+ }
+
// Set the "before" style, and compose the first part of the message
// to be used in our "is"/"isnot" invocations:
let msgPrefix = "Changing style ";
if (aTestcase.beforeStyle) {
- gElem.setAttribute("style", aTestcase.beforeStyle);
+ elem.setAttribute("style", aTestcase.beforeStyle);
msgPrefix += "from '" + aTestcase.beforeStyle + "' ";
}
- msgPrefix += "to '" + aTestcase.afterStyle + "' ";
+ msgPrefix += "on " + elem.nodeName + " ";
// Establish initial counts:
- let unusedVal = gElem.offsetHeight; // flush layout
+ let unusedVal = elem.offsetHeight; // flush layout
let origFramesConstructed = gUtils.framesConstructed;
let origFramesReflowed = gUtils.framesReflowed;
// Make the change and flush:
- gElem.setAttribute("style", aTestcase.afterStyle);
- unusedVal = gElem.offsetHeight; // flush layout
+ elem.setAttribute("style", aTestcase.afterStyle);
+ unusedVal = elem.offsetHeight; // flush layout
// Make our is/isnot assertions about whether things should have changed:
checkFinalCount(gUtils.framesConstructed, origFramesConstructed,
@@ -162,7 +257,7 @@ function runOneTest(aTestcase)
"reflow");
// Clean up!
- gElem.removeAttribute("style");
+ elem.removeAttribute("style");
}
gTestcases.forEach(runOneTest);
diff --git a/layout/style/test/test_viewport_scrollbar_causing_reflow.html b/layout/style/test/test_viewport_scrollbar_causing_reflow.html
new file mode 100644
index 000000000..dfd7ec450
--- /dev/null
+++ b/layout/style/test/test_viewport_scrollbar_causing_reflow.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1367568
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1367568</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug 1367568</a>
+<div id="content">
+ <!-- Some fixed-width divs that we shouldn't have to reflow when the viewport
+ changes: -->
+ <div style="width: 100px">
+ fixed-width
+ <div>(child)</div>
+ </div>
+ <div style="position: absolute; width: 150px">
+ abs-fixed-width
+ <div>(child)</div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1367568 **/
+
+/**
+ * This test verifies that "overflow" changes on the <body> don't cause
+ * an unnecessarily large amount of reflow.
+ */
+
+// Vars used in setStyleAndMeasure that we really only have to look up once:
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function setStyleAndMeasure(initialStyle, finalStyle) {
+ is(document.body.style.length, 0,
+ "Bug in test - body should start with empty style");
+ let unusedVal = document.body.offsetHeight; // flush layout
+ let constructCount = gUtils.framesConstructed;
+
+ document.body.style = initialStyle;
+ unusedVal = document.body.offsetHeight; // flush layout
+ let reflowCountBeforeTweak = gUtils.framesReflowed;
+
+ document.body.style = finalStyle;
+ unusedVal = document.body.offsetHeight; // flush layout
+ let reflowCountAfterTweak = gUtils.framesReflowed;
+
+ // Clean up:
+ document.body.style = "";
+
+ is(gUtils.framesConstructed, constructCount,
+ "Style tweak shouldn't have triggered frame construction");
+
+ // ...and return the delta:
+ return reflowCountAfterTweak - reflowCountBeforeTweak;
+}
+
+function main() {
+ // First, we sanity-check that our measurement make sense -- if we leave
+ // styles unchanged, we should measure no frames being reflowed:
+ let count = setStyleAndMeasure("width: 50px; height: 80px",
+ "width: 50px; height: 80px");
+ is(count, 0,
+ "Shouldn't reflow anything when we leave 'width' & 'height' unchanged");
+
+ // Now: see how many frames are reflowed when the "width" & "height" change.
+ // We'll use this as the reference when measuring reflow counts for various
+ // changes to "overflow" below.
+ count = setStyleAndMeasure("width: 50px; height: 80px",
+ "width: 90px; height: 60px");
+ ok(count > 0,
+ "Should reflow some frames when 'width' & 'height' change");
+
+ // Expected maximum number of frames reflowed for "overflow" changes
+ // (+2 is to allow for reflowing scrollbars themselves):
+ const expectedMax = count + 2;
+
+ // Shared ending for messages in all ok() checks below:
+ const messageSuffix =
+ " shouldn't be greater than count for tweaking width/height on body (" +
+ expectedMax + ")";
+
+ // OK, here is where the relevant tests actually begin!!
+ // See how many frames we reflow for various tweaks to "overflow" on
+ // the body -- we expect the count to be no larger than |expectedMax|.
+ count = setStyleAndMeasure("", "overflow: scroll");
+ ok(count <= expectedMax,
+ "Reflow count when setting 'overflow: scroll' on body (" + count + ")" +
+ messageSuffix);
+
+ count = setStyleAndMeasure("", "overflow: hidden");
+ ok(count <= expectedMax,
+ "Reflow count when setting 'overflow: hidden' on body (" + count + ")" +
+ messageSuffix);
+
+ // Test removal of "overflow: scroll":
+ count = setStyleAndMeasure("overflow: scroll", "");
+ ok(count <= expectedMax,
+ "Reflow count when removing 'overflow: scroll' from body (" + count + ")" +
+ messageSuffix);
+
+ count = setStyleAndMeasure("overflow: hidden", "");
+ ok(count <= expectedMax,
+ "Reflow count when removing 'overflow: hidden' from body (" + count + ")" +
+ messageSuffix);
+
+ // Test change between two non-'visible' overflow values:
+ count = setStyleAndMeasure("overflow: scroll", "overflow: hidden");
+ ok(count <= expectedMax,
+ "Reflow count when changing 'overflow' on body (" + count + ")" +
+ messageSuffix);
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>