author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Wed, 04 Nov 2015 11:59:46 +0100 | |
changeset 271123 | 6077f51254c69a1e14e1b61acba4af451bf1783e |
parent 271122 | cd5fab20f6bd128847ef8131c9aeed63cde019fc (current diff) |
parent 271023 | 287b4aa0d8653968e74930ce8cc2a61251f4170d (diff) |
child 271124 | 8868503916baa3e2eec135e5b9b140717e402171 |
child 271258 | 6e5296397487dc014b07a9cc9dfc7b938e2b4f17 |
child 271271 | d2906d28d9ed99ee4b2b93cc22bef718f3e81de4 |
push id | 67553 |
push user | cbook@mozilla.com |
push date | Wed, 04 Nov 2015 11:31:24 +0000 |
treeherder | mozilla-inbound@8868503916ba [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 45.0a1 |
first release with | nightly linux32
6077f51254c6
/
45.0a1
/
20151104030437
/
files
nightly linux64
6077f51254c6
/
45.0a1
/
20151104030437
/
files
nightly mac
6077f51254c6
/
45.0a1
/
20151104030437
/
files
nightly win32
6077f51254c6
/
45.0a1
/
20151104030437
/
files
nightly win64
6077f51254c6
/
45.0a1
/
20151104030437
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
45.0a1
/
20151104030437
/
pushlog to previous
nightly linux64
45.0a1
/
20151104030437
/
pushlog to previous
nightly mac
45.0a1
/
20151104030437
/
pushlog to previous
nightly win32
45.0a1
/
20151104030437
/
pushlog to previous
nightly win64
45.0a1
/
20151104030437
/
pushlog to previous
|
--- a/CLOBBER +++ b/CLOBBER @@ -17,9 +17,9 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # # Are you updating CLOBBER because you think it's needed for your WebIDL # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Merge day clobber +Bug 1214058 New xpcshell test not getting picked up
--- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -1233,35 +1233,37 @@ Accessible::ApplyARIAState(uint64_t* aSt // We only force the readonly bit off if we have a real mapping for the aria // role. This preserves the ability for screen readers to use readonly // (primarily on the document) as the hint for creating a virtual buffer. if (mRoleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY; if (mContent->HasID()) { - // If has a role & ID and aria-activedescendant on the container, assume focusable - nsIContent *ancestorContent = mContent; - while ((ancestorContent = ancestorContent->GetParent()) != nullptr) { - if (ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { - // ancestor has activedescendant property, this content could be active + // If has a role & ID and aria-activedescendant on the container, assume + // focusable. + const Accessible* ancestor = this; + while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { + dom::Element* el = ancestor->Elm(); + if (el && + el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { *aState |= states::FOCUSABLE; break; } } } } if (*aState & states::FOCUSABLE) { - // Special case: aria-disabled propagates from ancestors down to any focusable descendant - nsIContent *ancestorContent = mContent; - while ((ancestorContent = ancestorContent->GetParent()) != nullptr) { - if (ancestorContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, - nsGkAtoms::_true, eCaseMatters)) { - // ancestor has aria-disabled property, this is disabled + // Propogate aria-disabled from ancestors down to any focusable descendant. + const Accessible* ancestor = this; + while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { + dom::Element* el = ancestor->Elm(); + if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, + nsGkAtoms::_true, eCaseMatters)) { *aState |= states::UNAVAILABLE; break; } } } // special case: A native button element whose role got transformed by ARIA to a toggle button // Also applies to togglable button menus, like in the Dev Tools Web Console.
--- a/accessible/tests/mochitest/attributes/test_obj_group.html +++ b/accessible/tests/mochitest/attributes/test_obj_group.html @@ -186,16 +186,22 @@ testGroupAttrs("combo1_opt3", 3, 4); testGroupAttrs("combo1_opt4", 4, 4); ////////////////////////////////////////////////////////////////////////// // ARIA table testGroupAttrs("table_cell", 3, 4); testGroupAttrs("table_row", 2, 2); + ////////////////////////////////////////////////////////////////////////// + // ARIA list constructed by ARIA owns + testGroupAttrs("t1_li1", 1, 3); + testGroupAttrs("t1_li2", 2, 3); + testGroupAttrs("t1_li3", 3, 3); + // Test that group position information updates after deleting node. testGroupAttrs("tree4_ti1", 1, 2, 1); testGroupAttrs("tree4_ti2", 2, 2, 1); var tree4element = document.getElementById("tree4_ti1"); var tree4acc = getAccessible("tree4"); tree4element.parentNode.removeChild(tree4element); waitForEvent(EVENT_REORDER, tree4acc, function() { testGroupAttrs("tree4_ti2", 1, 1, 1); @@ -448,10 +454,16 @@ <input type="radio" id="radio5" name="group3"> </form> <div role="table" aria-colcount="4" aria-rowcount="2"> <div role="row" id="table_row" aria-rowindex="2"> <div role="cell" id="table_cell" aria-colindex="3">cell</div> </div> </div> + + <div role="list" aria-owns="t1_li1 t1_li2 t1_li3"> + <div role="listitem" id="t1_li2">Apples</div> + <div role="listitem" id="t1_li1">Oranges</div> + </span> + <div role="listitem" id="t1_li3">Bananas</div> </body> </html>
--- a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html +++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html @@ -46,41 +46,42 @@ https://bugzilla.mozilla.org/show_bug.cg new focusChecker(aNewItemID) ]; this.invoke = function insertItemNFocus_invoke() { var container = getNode(aID); var itemNode = document.createElement("div"); itemNode.setAttribute("id", aNewItemID); - itemNode.textContent = "item3"; + itemNode.textContent = aNewItemID; container.appendChild(itemNode); container.setAttribute("aria-activedescendant", aNewItemID); } this.getID = function insertItemNFocus_getID() { return "insert new node and focus it with ID: " + aNewItemID; } } var gQueue = null; function doTest() { gQueue = new eventQueue(); - gQueue.push(new synthFocus("container", new focusChecker("item1"))); - gQueue.push(new changeARIAActiveDescendant("container", "item2")); + gQueue.push(new synthFocus("listbox", new focusChecker("item1"))); + gQueue.push(new changeARIAActiveDescendant("listbox", "item2")); + gQueue.push(new changeARIAActiveDescendant("listbox", "item3")); gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry"))); gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2")); todo(false, "No focus for inserted element, bug 687011"); - //gQueue.push(new insertItemNFocus("container", "item3")); + //gQueue.push(new insertItemNFocus("listbox", "item4")); gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> @@ -96,20 +97,22 @@ https://bugzilla.mozilla.org/show_bug.cg title="Focus may be missed when ARIA active-descendant is changed on active composite widget"> Mozilla Bug 761102 </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> - <div role="listbox" aria-activedescendant="item1" id="container" tabindex="1"> + <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1" + aria-owns="item3"> <div role="listitem" id="item1">item1</div> <div role="listitem" id="item2">item2</div> </div> + <div role="listitem" id="item3">item3</div> <div role="combobox" id="combobox"> <input id="combobox_entry"> <ul> <li role="option" id="combobox_option1">option1</li> <li role="option" id="combobox_option2">option2</li> </ul> </div>
--- a/accessible/tests/mochitest/states/test_aria.html +++ b/accessible/tests/mochitest/states/test_aria.html @@ -495,24 +495,26 @@ <div role="listbox"> <div id="aria_selectable_listitem" role="option" aria-selected="true">Item1</div> </div> <!-- Test that aria-disabled state gets propagated to all descendants --> <div id="group" role="group" aria-disabled="true"> <button>hi</button> - <div tabindex="0" role="listbox" aria-activedescendant="item1"> + <div tabindex="0" role="listbox" aria-activedescendant="item1" + aria-owns="item5"> <div role="option" id="item1">Item 1</div> <div role="option" id="item2">Item 2</div> <div role="option" id="item3">Item 3</div> <div role="option" id="item4">Item 4</div> </div> <div role="slider" tabindex="0">A slider</div> </div> + <div role="option" id="item5">Item 5</div> <!-- Test active state --> <div id="as_listbox" tabindex="0" role="listbox" aria-activedescendant="as_item1"> <div role="option" id="as_item1">Item 1</div> <div role="option" id="as_item2">Item 2</div> </div>
--- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -2929,18 +2929,18 @@ var SessionStoreInternal = { // It's important to set the window state to dirty so that // we collect their data for the first time when saving state. DirtyWindows.add(window); // In case we didn't collect/receive data for any tabs yet we'll have to // fill the array with at least empty tabData objects until |_tPos| or // we'll end up with |null| entries. - for (let tab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) { - let emptyState = {entries: [], lastAccessed: tab.lastAccessed}; + for (let otherTab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) { + let emptyState = {entries: [], lastAccessed: otherTab.lastAccessed}; this._windows[window.__SSi].tabs.push(emptyState); } // Update the tab state in case we shut down without being notified. this._windows[window.__SSi].tabs[tab._tPos] = tabData; // Prepare the tab so that it can be properly restored. We'll pin/unpin // and show/hide tabs as necessary. We'll also attach a copy of the tab's
--- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -2157,16 +2157,16 @@ if (AppConstants.MOZ_SERVICES_HEALTHREPO // our fields are dynamic fields: { }, // We need a custom serializer because the default one doesn't accept unknown fields _serializeJSONDaily: function(data) { let result = {_v: this.version }; - for (let [field, data] of data) { - result[field] = data; + for (let [field, value] of data) { + result[field] = value; } return result; } }); }
--- a/browser/config/tooltool-manifests/linux64/asan.manifest +++ b/browser/config/tooltool-manifests/linux64/asan.manifest @@ -3,10 +3,18 @@ "clang_version": "r200213" }, { "size": 71282740, "digest": "ee9edb1ef3afd9ab29e39565145545ad57e8d8d2538be4d822d7dbd64038f4529b0b287cecf48bf83def52a26ac2c6faa331686c3ad5e8b4ba4c22686ee0808f", "algorithm": "sha512", "filename": "clang.tar.bz2", "unpack": true +}, +{ +"size": 12057960, +"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"setup": "setup.sh", +"unpack": true } ]
--- a/build/sanitizers/lsan_suppressions.txt +++ b/build/sanitizers/lsan_suppressions.txt @@ -104,15 +104,16 @@ leak:nsBaseWidget::StoreWindowClipRegion ### leak:libcairo.so leak:libdl.so leak:libdricore.so leak:libGL.so leak:libglib-2.0.so leak:libp11-kit.so +leak:libpixman-1.so leak:libpulse.so leak:libpulsecommon-1.1.so leak:libresolv.so leak:libstdc++.so leak:libXrandr.so leak:pthread_setspecific_internal leak:swrast_dri.so
--- a/build/unix/mozconfig.gtk +++ b/build/unix/mozconfig.gtk @@ -1,30 +1,29 @@ +# To do try builds with Gtk+2, uncomment the following line, and remove +# everything after that. +#ac_add_options --enable-default-toolkit=cairo-gtk2 + TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} -# $TOOLTOOL_DIR/gtk3 comes from tooltool, when the tooltool manifest contains it. -if [ -d "$TOOLTOOL_DIR/gtk3" ]; then - if [ -z "$PKG_CONFIG_LIBDIR" ]; then - echo PKG_CONFIG_LIBDIR must be set >&2 - exit 1 - fi - export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3" - export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig" - PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config" - export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}" - # Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages. - LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}" - ac_add_options --enable-default-toolkit=cairo-gtk3 +# $TOOLTOOL_DIR/gtk3 comes from tooltool, and must be included in the tooltool manifest. +if [ -z "$PKG_CONFIG_LIBDIR" ]; then + echo PKG_CONFIG_LIBDIR must be set >&2 + exit 1 +fi +export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3" +export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig" +PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config" +export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}" +# Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages. +LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}" +ac_add_options --enable-default-toolkit=cairo-gtk3 - # Set things up to use Gtk+3 from the tooltool package - mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts" - mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc" - mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib" - mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" - mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders" - mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib" +# Set things up to use Gtk+3 from the tooltool package +mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts" +mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc" +mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib" +mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" +mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders" +mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib" - # Until a tooltool with bug 1188571 landed is available everywhere - $TOOLTOOL_DIR/gtk3/setup.sh -else - PKG_CONFIG=pkg-config - ac_add_options --enable-default-toolkit=cairo-gtk2 -fi +# Until a tooltool with bug 1188571 landed is available everywhere +$TOOLTOOL_DIR/gtk3/setup.sh
--- a/config/rules.mk +++ b/config/rules.mk @@ -1336,34 +1336,30 @@ endif # # We use $(CURDIR) in the rule's target to ensure that we don't find # a dependency directory in the source tree via VPATH (perhaps from # a previous build in the source tree) and thus neglect to create a # dependency directory in the object directory, where we really need # it. ifneq (,$(filter-out all chrome default export realchrome clean clobber clobber_all distclean realclean,$(MAKECMDGOALS))) -MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_MDDEPEND_FILES) $(addsuffix .pp,$(notdir $(sort $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS))))))) +MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(addsuffix .pp,$(notdir $(sort $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS))))))) ifneq (,$(MDDEPEND_FILES)) $(call include_deps,$(MDDEPEND_FILES)) endif endif - -ifneq (,$(filter export,$(MAKECMDGOALS))) -MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_EXPORT_MDDEPEND_FILES)))) +MDDEPEND_FILES := $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_MDDEPEND_FILES)))) ifneq (,$(MDDEPEND_FILES)) $(call include_deps,$(MDDEPEND_FILES)) endif -endif - ############################################################################# -include $(topsrcdir)/$(MOZ_BUILD_APP)/app-rules.mk -include $(MY_RULES) # # Generate Emacs tags in a file named TAGS if ETAGS was set in $(MY_CONFIG) # or in $(MY_RULES)
--- a/devtools/client/tilt/TiltWorkerCrafter.js +++ b/devtools/client/tilt/TiltWorkerCrafter.js @@ -260,17 +260,18 @@ self.random = { /** * From http://baagoe.com/en/RandomMusings/javascript * Johannes Baagoe <baagoe@baagoe.com>, 2010 */ mash: function RNG_mash(data) { let h, n = 0xefc8249d; - for (let i = 0, data = data.toString(), len = data.length; i < len; i++) { + data = data.toString(); + for (let i = 0, len = data.length; i < len; i++) { n += data.charCodeAt(i); h = 0.02519603282416938 * n; n = h >>> 0; h -= n; h *= n; n = h >>> 0; h -= n; n += h * 0x100000000; // 2^32
--- a/devtools/shared/heapsnapshot/CensusUtils.js +++ b/devtools/shared/heapsnapshot/CensusUtils.js @@ -121,18 +121,18 @@ exports.getReportEdges = getReportEdges; function recursiveWalk(breakdown, edge, report, visitor) { if (breakdown.by === "count") { visitor.enter(breakdown, report, edge); visitor.count(breakdown, report, edge); visitor.exit(breakdown, report, edge); } else { visitor.enter(breakdown, report, edge); - for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) { - recursiveWalk(breakdown, edge, referent, visitor); + for (let { edge, referent, breakdown: subBreakdown } of getReportEdges(breakdown, report)) { + recursiveWalk(subBreakdown, edge, referent, visitor); } visitor.exit(breakdown, report, edge); } }; /** * Walk the given `report` that was generated by taking a census with the * specified `breakdown`.
--- a/dom/base/ConsoleReportCollector.cpp +++ b/dom/base/ConsoleReportCollector.cpp @@ -74,13 +74,34 @@ ConsoleReportCollector::FlushConsoleRepo aDocument, report.mPropertiesFile, report.mMessageName.get(), params.get(), paramsLength, uri, EmptyString(), report.mLineNumber, report.mColumnNumber); } } +void +ConsoleReportCollector::FlushConsoleReports(nsIConsoleReportCollector* aCollector) +{ + MOZ_ASSERT(aCollector); + + nsTArray<PendingReport> reports; + + { + MutexAutoLock lock(mMutex); + mPendingReports.SwapElements(reports); + } + + for (uint32_t i = 0; i < reports.Length(); ++i) { + PendingReport& report = reports[i]; + aCollector->AddConsoleReport(report.mErrorFlags, report.mCategory, + report.mPropertiesFile, report.mSourceFileURI, + report.mLineNumber, report.mColumnNumber, + report.mMessageName, report.mStringParams); + } +} + ConsoleReportCollector::~ConsoleReportCollector() { } } // namespace mozilla
--- a/dom/base/ConsoleReportCollector.h +++ b/dom/base/ConsoleReportCollector.h @@ -24,16 +24,19 @@ public: const nsACString& aSourceFileURI, uint32_t aLineNumber, uint32_t aColumnNumber, const nsACString& aMessageName, const nsTArray<nsString>& aStringParams) override; void FlushConsoleReports(nsIDocument* aDocument) override; + void + FlushConsoleReports(nsIConsoleReportCollector* aCollector) override; + private: ~ConsoleReportCollector(); struct PendingReport { PendingReport(uint32_t aErrorFlags, const nsACString& aCategory, nsContentUtils::PropertiesFile aPropertiesFile, const nsACString& aSourceFileURI, uint32_t aLineNumber,
--- a/dom/base/nsContentPermissionHelper.cpp +++ b/dom/base/nsContentPermissionHelper.cpp @@ -27,16 +27,17 @@ #include "nsIMutableArray.h" #include "nsContentPermissionHelper.h" #include "nsJSUtils.h" #include "nsISupportsPrimitives.h" #include "nsServiceManagerUtils.h" #include "nsIDocument.h" #include "nsIDOMEvent.h" #include "nsWeakPtr.h" +#include "ScriptSettings.h" using mozilla::Unused; // <snicker> using namespace mozilla::dom; using namespace mozilla; #define kVisibilityChange "visibilitychange" NS_IMPL_ISUPPORTS(VisibilityChangeListener, nsIDOMEventListener) @@ -645,28 +646,33 @@ nsContentPermissionRequestProxy::Allow(J nsTArray<PermissionChoice> choices; if (aChoices.isNullOrUndefined()) { // No choice is specified. } else if (aChoices.isObject()) { // Iterate through all permission types. for (uint32_t i = 0; i < mPermissionRequests.Length(); ++i) { nsCString type = mPermissionRequests[i].type(); - mozilla::AutoSafeJSContext cx; + AutoJSAPI jsapi; + jsapi.Init(); + + JSContext* cx = jsapi.cx(); JS::Rooted<JSObject*> obj(cx, &aChoices.toObject()); JSAutoCompartment ac(cx, obj); JS::Rooted<JS::Value> val(cx); if (!JS_GetProperty(cx, obj, type.BeginReading(), &val) || !val.isString()) { - // no setting for the permission type, skip it + // no setting for the permission type, clear exception and skip it + jsapi.ClearException(); } else { nsAutoJSString choice; if (!choice.init(cx, val)) { + jsapi.ClearException(); return NS_ERROR_FAILURE; } choices.AppendElement(PermissionChoice(type, choice)); } } } else { MOZ_ASSERT(false, "SelectedChoices should be undefined or an JS object"); return NS_ERROR_FAILURE;
--- a/dom/base/nsIConsoleReportCollector.h +++ b/dom/base/nsIConsoleReportCollector.h @@ -61,21 +61,29 @@ public: Params... aParams) { nsTArray<nsString> params; mozilla::dom::StringArrayAppender::Append(params, sizeof...(Params), aParams...); AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile, aSourceFileURI, aLineNumber, aColumnNumber, aMessageName, params); } - // Flush all pending reports to the console. + // Flush all pending reports to the console. Main thread only. // // aDocument An optional document representing where to flush the // reports. If provided, then the corresponding window's // web console will get the reports. Otherwise the reports // go to the browser console. virtual void FlushConsoleReports(nsIDocument* aDocument) = 0; + + // Flush all pending reports to another collector. May be called from any + // thread. + // + // aCollector A required collector object that will effectively take + // ownership of our currently console reports. + virtual void + FlushConsoleReports(nsIConsoleReportCollector* aCollector) = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIConsoleReportCollector, NS_NSICONSOLEREPORTCOLLECTOR_IID) #endif // nsIConsoleReportCollector_h
--- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -324,11 +324,17 @@ JSObject* GetDefaultScopeFromJSContext(J // the cx, so in those cases we need to fetch it via the scx // instead. nsIScriptContext *scx = GetScriptContextFromJSContext(cx); return scx ? scx->GetWindowProxy() : nullptr; } bool nsAutoJSString::init(const JS::Value &v) { - return init(nsContentUtils::RootingCxForThread(), v); + JSContext* cx = nsContentUtils::RootingCxForThread(); + if (!init(nsContentUtils::RootingCxForThread(), v)) { + JS_ClearPendingException(cx); + return false; + } + + return true; }
--- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -97,16 +97,18 @@ skip-if = (os == "android") || (toolkit [test_browserElement_oop_TargetBlank.html] skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_TargetTop.html] [test_browserElement_oop_Titlechange.html] [test_browserElement_oop_TopBarrier.html] [test_browserElement_oop_VisibilityChange.html] [test_browserElement_oop_XFrameOptions.html] [test_browserElement_oop_XFrameOptionsAllowFrom.html] +# bug 1189592 +skip-if = asan [test_browserElement_oop_XFrameOptionsDeny.html] [test_browserElement_oop_XFrameOptionsSameOrigin.html] # Disabled until bug 930449 makes it stop timing out [test_browserElement_oop_ContextmenuEvents.html] disabled = bug 930449 # Disabled until bug 924771 makes them stop timing out [test_browserElement_oop_CloseFromOpener.html] disabled = bug 924771
--- a/dom/canvas/CanvasImageCache.cpp +++ b/dom/canvas/CanvasImageCache.cpp @@ -16,45 +16,54 @@ #include "gfx2DGlue.h" namespace mozilla { using namespace dom; using namespace gfx; struct ImageCacheKey { - ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) - : mImage(aImage), mCanvas(aCanvas) {} + ImageCacheKey(Element* aImage, + HTMLCanvasElement* aCanvas, + bool aIsAccelerated) + : mImage(aImage) + , mCanvas(aCanvas) + , mIsAccelerated(aIsAccelerated) + {} Element* mImage; HTMLCanvasElement* mCanvas; + bool mIsAccelerated; }; struct ImageCacheEntryData { ImageCacheEntryData(const ImageCacheEntryData& aOther) : mImage(aOther.mImage) , mILC(aOther.mILC) , mCanvas(aOther.mCanvas) + , mIsAccelerated(aOther.mIsAccelerated) , mRequest(aOther.mRequest) , mSourceSurface(aOther.mSourceSurface) , mSize(aOther.mSize) {} explicit ImageCacheEntryData(const ImageCacheKey& aKey) : mImage(aKey.mImage) , mILC(nullptr) , mCanvas(aKey.mCanvas) + , mIsAccelerated(aKey.mIsAccelerated) {} nsExpirationState* GetExpirationState() { return &mState; } size_t SizeInBytes() { return mSize.width * mSize.height * 4; } // Key RefPtr<Element> mImage; nsIImageLoadingContent* mILC; RefPtr<HTMLCanvasElement> mCanvas; + bool mIsAccelerated; // Value nsCOMPtr<imgIRequest> mRequest; RefPtr<SourceSurface> mSourceSurface; IntSize mSize; nsExpirationState mState; }; class ImageCacheEntry : public PLDHashEntryHdr { @@ -65,56 +74,71 @@ public: explicit ImageCacheEntry(const KeyType* aKey) : mData(new ImageCacheEntryData(*aKey)) {} ImageCacheEntry(const ImageCacheEntry &toCopy) : mData(new ImageCacheEntryData(*toCopy.mData)) {} ~ImageCacheEntry() {} bool KeyEquals(KeyTypePointer key) const { - return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas; + return mData->mImage == key->mImage && + mData->mCanvas == key->mCanvas && + mData->mIsAccelerated == key->mIsAccelerated; } static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } static PLDHashNumber HashKey(KeyTypePointer key) { - return HashGeneric(key->mImage, key->mCanvas); + return HashGeneric(key->mImage, key->mCanvas, key->mIsAccelerated); } enum { ALLOW_MEMMOVE = true }; nsAutoPtr<ImageCacheEntryData> mData; }; +struct SimpleImageCacheKey { + SimpleImageCacheKey(const imgIRequest* aImage, + bool aIsAccelerated) + : mImage(aImage) + , mIsAccelerated(aIsAccelerated) + {} + const imgIRequest* mImage; + bool mIsAccelerated; +}; + class SimpleImageCacheEntry : public PLDHashEntryHdr { public: - typedef imgIRequest& KeyType; - typedef const imgIRequest* KeyTypePointer; + typedef SimpleImageCacheKey KeyType; + typedef const SimpleImageCacheKey* KeyTypePointer; explicit SimpleImageCacheEntry(KeyTypePointer aKey) - : mRequest(const_cast<imgIRequest*>(aKey)) + : mRequest(const_cast<imgIRequest*>(aKey->mImage)) + , mIsAccelerated(aKey->mIsAccelerated) {} SimpleImageCacheEntry(const SimpleImageCacheEntry &toCopy) : mRequest(toCopy.mRequest) + , mIsAccelerated(toCopy.mIsAccelerated) , mSourceSurface(toCopy.mSourceSurface) {} ~SimpleImageCacheEntry() {} bool KeyEquals(KeyTypePointer key) const { - return key == mRequest; + return key->mImage == mRequest && key->mIsAccelerated == mIsAccelerated; } - static KeyTypePointer KeyToPointer(KeyType key) { return &key; } + static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } static PLDHashNumber HashKey(KeyTypePointer key) { - return NS_PTR_TO_UINT32(key) >> 2; + return HashGeneric(key->mImage, key->mIsAccelerated); } enum { ALLOW_MEMMOVE = true }; nsCOMPtr<imgIRequest> mRequest; + bool mIsAccelerated; RefPtr<SourceSurface> mSourceSurface; }; static bool sPrefsInitialized = false; static int32_t sCanvasImageCacheLimit = 0; class ImageCacheObserver; @@ -125,18 +149,18 @@ public: ImageCache(); ~ImageCache(); virtual void NotifyExpired(ImageCacheEntryData* aObject) { mTotal -= aObject->SizeInBytes(); RemoveObject(aObject); // Deleting the entry will delete aObject since the entry owns aObject - mSimpleCache.RemoveEntry(*aObject->mRequest); - mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas)); + mSimpleCache.RemoveEntry(SimpleImageCacheKey(aObject->mRequest, aObject->mIsAccelerated)); + mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas, aObject->mIsAccelerated)); } nsTHashtable<ImageCacheEntry> mCache; nsTHashtable<SimpleImageCacheEntry> mSimpleCache; size_t mTotal; RefPtr<ImageCacheObserver> mImageCacheObserver; }; @@ -232,99 +256,104 @@ ImageCache::~ImageCache() { mImageCacheObserver->Destroy(); } void CanvasImageCache::NotifyDrawImage(Element* aImage, HTMLCanvasElement* aCanvas, imgIRequest* aRequest, SourceSurface* aSource, - const IntSize& aSize) + const IntSize& aSize, + bool aIsAccelerated) { if (!gImageCache) { gImageCache = new ImageCache(); nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); } - ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); + ImageCacheEntry* entry = + gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas, aIsAccelerated)); if (entry) { if (entry->mData->mSourceSurface) { // We are overwriting an existing entry. gImageCache->mTotal -= entry->mData->SizeInBytes(); gImageCache->RemoveObject(entry->mData); - gImageCache->mSimpleCache.RemoveEntry(*entry->mData->mRequest); + gImageCache->mSimpleCache.RemoveEntry(SimpleImageCacheKey(entry->mData->mRequest, entry->mData->mIsAccelerated)); } gImageCache->AddObject(entry->mData); nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage); if (ilc) { ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(entry->mData->mRequest)); } entry->mData->mILC = ilc; entry->mData->mSourceSurface = aSource; entry->mData->mSize = aSize; gImageCache->mTotal += entry->mData->SizeInBytes(); if (entry->mData->mRequest) { SimpleImageCacheEntry* simpleentry = - gImageCache->mSimpleCache.PutEntry(*entry->mData->mRequest); + gImageCache->mSimpleCache.PutEntry(SimpleImageCacheKey(entry->mData->mRequest, aIsAccelerated)); simpleentry->mSourceSurface = aSource; } } if (!sCanvasImageCacheLimit) return; // Expire the image cache early if its larger than we want it to be. while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit)) gImageCache->AgeOneGeneration(); } SourceSurface* CanvasImageCache::Lookup(Element* aImage, HTMLCanvasElement* aCanvas, - gfx::IntSize* aSize) + gfx::IntSize* aSize, + bool aIsAccelerated) { if (!gImageCache) return nullptr; - ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas)); + ImageCacheEntry* entry = + gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas, aIsAccelerated)); if (!entry || !entry->mData->mILC) return nullptr; nsCOMPtr<imgIRequest> request; entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request)); if (request != entry->mData->mRequest) return nullptr; gImageCache->MarkUsed(entry->mData); *aSize = entry->mData->mSize; return entry->mData->mSourceSurface; } SourceSurface* -CanvasImageCache::SimpleLookup(Element* aImage) +CanvasImageCache::SimpleLookup(Element* aImage, + bool aIsAccelerated) { if (!gImageCache) return nullptr; nsCOMPtr<imgIRequest> request; nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage); if (!ilc) return nullptr; ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request)); if (!request) return nullptr; - SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(*request); + SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(SimpleImageCacheKey(request, aIsAccelerated)); if (!entry) return nullptr; return entry->mSourceSurface; } NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver)
--- a/dom/canvas/CanvasImageCache.h +++ b/dom/canvas/CanvasImageCache.h @@ -28,31 +28,34 @@ public: * Notify that image element aImage was (or is about to be) drawn to aCanvas * using the first frame of aRequest's image. The data for the surface is * in aSurface, and the image size is in aSize. */ static void NotifyDrawImage(dom::Element* aImage, dom::HTMLCanvasElement* aCanvas, imgIRequest* aRequest, SourceSurface* aSource, - const gfx::IntSize& aSize); + const gfx::IntSize& aSize, + bool aIsAccelerated); /** * Check whether aImage has recently been drawn into aCanvas. If we return * a non-null surface, then the image was recently drawn into the canvas * (with the same image request) and the returned surface contains the image * data, and the image size will be returned in aSize. */ static SourceSurface* Lookup(dom::Element* aImage, dom::HTMLCanvasElement* aCanvas, - gfx::IntSize* aSize); + gfx::IntSize* aSize, + bool aIsAccelerated); /** * This is the same as Lookup, except it works on any image recently drawn * into any canvas. Security checks need to be done again if using the * results from this. */ - static SourceSurface* SimpleLookup(dom::Element* aImage); + static SourceSurface* SimpleLookup(dom::Element* aImage, + bool aIsAccelerated); }; } // namespace mozilla #endif /* CANVASIMAGECACHE_H_ */
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -4307,17 +4307,18 @@ CanvasRenderingContext2D::CachedSurfaceF } nsCOMPtr<nsIPrincipal> principal; if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) || !principal) { return res; } - res.mSourceSurface = CanvasImageCache::SimpleLookup(aElement); + res.mSourceSurface = + CanvasImageCache::SimpleLookup(aElement, mIsSkiaGL); if (!res.mSourceSurface) { return res; } int32_t corsmode = imgIRequest::CORS_NONE; if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) { res.mCORSUsed = corsmode != imgIRequest::CORS_NONE; } @@ -4413,17 +4414,17 @@ CanvasRenderingContext2D::DrawImage(cons HTMLImageElement* img = &image.GetAsHTMLImageElement(); element = img; } else { HTMLVideoElement* video = &image.GetAsHTMLVideoElement(); element = video; } srcSurf = - CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); + CanvasImageCache::Lookup(element, mCanvasElement, &imgSize, mIsSkiaGL); } nsLayoutUtils::DirectDrawInfo drawInfo; #ifdef USE_SKIA_GPU if (mRenderingMode == RenderingMode::OpenGLBackendMode && !srcSurf && image.IsHTMLVideoElement() && @@ -4561,17 +4562,17 @@ CanvasRenderingContext2D::DrawImage(cons CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, res.mPrincipal, res.mIsWriteOnly, res.mCORSUsed); } if (res.mSourceSurface) { if (res.mImageRequest) { CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest, - res.mSourceSurface, imgSize); + res.mSourceSurface, imgSize, mIsSkiaGL); } srcSurf = res.mSourceSurface; } else { drawInfo = res.mDrawInfo; } }
--- a/dom/canvas/test/_webgl-conformance.ini +++ b/dom/canvas/test/_webgl-conformance.ini @@ -503,16 +503,17 @@ support-files = webgl-conformance/../web skip-if = os == 'android' [webgl-conformance/_wrappers/test_conformance__canvas__buffer-preserve-test.html] [webgl-conformance/_wrappers/test_conformance__canvas__canvas-test.html] [webgl-conformance/_wrappers/test_conformance__canvas__canvas-zero-size.html] [webgl-conformance/_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html] skip-if = os == 'mac' [webgl-conformance/_wrappers/test_conformance__canvas__drawingbuffer-test.html] [webgl-conformance/_wrappers/test_conformance__canvas__viewport-unchanged-upon-resize.html] +skip-if = os == 'mac' [webgl-conformance/_wrappers/test_conformance__context__constants.html] [webgl-conformance/_wrappers/test_conformance__context__context-attributes-alpha-depth-stencil-antialias.html] skip-if = (os == 'b2g') [webgl-conformance/_wrappers/test_conformance__context__context-lost-restored.html] [webgl-conformance/_wrappers/test_conformance__context__context-lost.html] [webgl-conformance/_wrappers/test_conformance__context__context-type-test.html] [webgl-conformance/_wrappers/test_conformance__context__incorrect-context-object-behaviour.html] [webgl-conformance/_wrappers/test_conformance__context__methods.html]
--- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini +++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini @@ -103,11 +103,14 @@ skip-if = (os == 'b2g') # Failures after enabling color_buffer_[half_]float. fail-if = (os == 'linux') ######################################################################## # Mac [_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html] # Intermittent crash on OSX. skip-if = os == 'mac' +[_wrappers/test_conformance__canvas__viewport-unchanged-upon-resize.html] +# New OSX r7 machines and 10.10.5 is causing perma failure (bug 1216549) +skip-if = os == 'mac' ######################################################################## # Win
--- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -2552,16 +2552,18 @@ EventStateManager::DoScrollText(nsIScrol bool isDeltaModePixel = (aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL); nsIScrollableFrame::ScrollMode mode; switch (aEvent->scrollType) { case WidgetWheelEvent::SCROLL_DEFAULT: if (isDeltaModePixel) { mode = nsIScrollableFrame::NORMAL; + } else if (aEvent->mFlags.mHandledByAPZ) { + mode = nsIScrollableFrame::SMOOTH_MSD; } else { mode = nsIScrollableFrame::SMOOTH; } break; case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY: mode = nsIScrollableFrame::INSTANT; break; case WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY: @@ -3114,16 +3116,24 @@ EventStateManager::PostHandleEvent(nsPre nsPluginFrame* pluginFrame = do_QueryFrame(frameToScroll); // When APZ is enabled, the actual scroll animation might be handled by // the compositor. WheelPrefs::Action action; if (pluginFrame) { MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction()); action = WheelPrefs::ACTION_SEND_TO_PLUGIN; + } else if (nsLayoutUtils::IsScrollFrameWithSnapping(frameToScroll)) { + // If the target has scroll-snapping points then we want to handle + // the wheel event on the main thread even if we have APZ enabled. Do + // so and let the APZ know that it should ignore this event. + if (wheelEvent->mFlags.mHandledByAPZ) { + wheelEvent->mFlags.mDefaultPrevented = true; + } + action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent); } else if (wheelEvent->mFlags.mHandledByAPZ) { action = WheelPrefs::ACTION_NONE; } else { action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent); } switch (action) { case WheelPrefs::ACTION_SCROLL: { // For scrolling of default action, we should honor the mouse wheel
--- a/dom/ipc/ProcessHangMonitor.cpp +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -454,35 +454,32 @@ HangMonitorParent::HangMonitorParent(Pro mMonitor("HangMonitorParent lock"), mShutdownDone(false), mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock") { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false); } -static PLDHashOperator -DeleteMinidump(const uint32_t& aPluginId, nsString aCrashId, void* aUserData) -{ -#ifdef MOZ_CRASHREPORTER - if (!aCrashId.IsEmpty()) { - CrashReporter::DeleteMinidumpFilesForID(aCrashId); - } -#endif - return PL_DHASH_NEXT; -} - HangMonitorParent::~HangMonitorParent() { // For some reason IPDL doesn't automatically delete the channel for a // bridged protocol (bug 1090570). So we have to do it ourselves. XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(GetTransport())); +#ifdef MOZ_CRASHREPORTER MutexAutoLock lock(mBrowserCrashDumpHashLock); - mBrowserCrashDumpIds.EnumerateRead(DeleteMinidump, nullptr); + + for (auto iter = mBrowserCrashDumpIds.Iter(); !iter.Done(); iter.Next()) { + nsString crashId = iter.UserData(); + if (!crashId.IsEmpty()) { + CrashReporter::DeleteMinidumpFilesForID(crashId); + } + } +#endif } void HangMonitorParent::Shutdown() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MonitorAutoLock lock(mMonitor);
--- a/dom/ipc/ProcessPriorityManager.cpp +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -594,47 +594,29 @@ ProcessPriorityManagerImpl::ObserveConte pppm->ShutDown(); mParticularManagers.Remove(childID); mHighPriorityChildIDs.RemoveEntry(childID); } } -static PLDHashOperator -FreezeParticularProcessPriorityManagers( - const uint64_t& aKey, - RefPtr<ParticularProcessPriorityManager> aValue, - void* aUserData) -{ - aValue->Freeze(); - return PL_DHASH_NEXT; -} - -static PLDHashOperator -UnfreezeParticularProcessPriorityManagers( - const uint64_t& aKey, - RefPtr<ParticularProcessPriorityManager> aValue, - void* aUserData) -{ - aValue->Unfreeze(); - return PL_DHASH_NEXT; -} - void ProcessPriorityManagerImpl::ObserveScreenStateChanged(const char16_t* aData) { if (NS_LITERAL_STRING("on").Equals(aData)) { sFrozen = false; - mParticularManagers.EnumerateRead( - &UnfreezeParticularProcessPriorityManagers, nullptr); + for (auto iter = mParticularManagers.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->Unfreeze(); + } } else { sFrozen = true; - mParticularManagers.EnumerateRead( - &FreezeParticularProcessPriorityManagers, nullptr); + for (auto iter = mParticularManagers.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->Freeze(); + } } } bool ProcessPriorityManagerImpl::ChildProcessHasHighPriority( void ) { return mHighPriorityChildIDs.Count() > 0; }
--- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -193,24 +193,24 @@ VideoData::ShallowCopyUpdateTimestampAnd aOther->mDisplay, aOther->mFrameID); v->mDiscontinuity = aOther->mDiscontinuity; v->mImage = aOther->mImage; return v.forget(); } /* static */ -void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, +bool VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, const VideoInfo& aInfo, const YCbCrBuffer &aBuffer, const IntRect& aPicture, bool aCopyData) { if (!aVideoImage) { - return; + return false; } const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0]; const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1]; const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2]; PlanarYCbCrData data; data.mYChannel = Y.mData + Y.mOffset; data.mYSize = IntSize(Y.mWidth, Y.mHeight); @@ -224,19 +224,19 @@ void VideoData::SetVideoDataToImage(Plan data.mCrSkip = Cr.mSkip; data.mPicX = aPicture.x; data.mPicY = aPicture.y; data.mPicSize = aPicture.Size(); data.mStereoMode = aInfo.mStereoMode; aVideoImage->SetDelayedConversion(true); if (aCopyData) { - aVideoImage->SetData(data); + return aVideoImage->SetData(data); } else { - aVideoImage->SetDataNoCopy(data); + return aVideoImage->SetDataNoCopy(data); } } /* static */ already_AddRefed<VideoData> VideoData::Create(const VideoInfo& aInfo, ImageContainer* aContainer, Image* aImage, @@ -325,34 +325,34 @@ VideoData::Create(const VideoInfo& aInfo if (!v->mImage) { return nullptr; } NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR || v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR, "Wrong format?"); PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get()); - if (!aImage) { - VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, - true /* aCopyData */); - } else { - VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, - false /* aCopyData */); + bool shouldCopyData = (aImage == nullptr); + if (!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, + shouldCopyData)) { + return nullptr; } #ifdef MOZ_WIDGET_GONK if (!videoImage->IsValid() && !aImage && IsYV12Format(Y, Cb, Cr)) { // Failed to allocate gralloc. Try fallback. v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR); if (!v->mImage) { return nullptr; } videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get()); - VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, - true /* aCopyData */); + if(!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, + true /* aCopyData */)) { + return nullptr; + } } #endif return v.forget(); } /* static */ already_AddRefed<VideoData> VideoData::Create(const VideoInfo& aInfo, @@ -468,17 +468,19 @@ VideoData::Create(const VideoInfo& aInfo "Wrong format?"); typedef mozilla::layers::GrallocImage GrallocImage; GrallocImage* videoImage = static_cast<GrallocImage*>(v->mImage.get()); GrallocImage::GrallocData data; data.mPicSize = aPicture.Size(); data.mGraphicBuffer = aBuffer; - videoImage->SetData(data); + if (!videoImage->SetData(data)) { + return nullptr; + } return v.forget(); } #endif // MOZ_OMX_DECODER // Alignment value - 1. 0 means that data isn't aligned. // For 32-bytes aligned, use 31U. #define RAW_DATA_ALIGNMENT 31U
--- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -278,17 +278,17 @@ public: // specified timestamp and duration. All data from aOther is copied // into the new VideoData, as ShallowCopyUpdateDuration() does. static already_AddRefed<VideoData> ShallowCopyUpdateTimestampAndDuration(const VideoData* aOther, int64_t aTimestamp, int64_t aDuration); // Initialize PlanarYCbCrImage. Only When aCopyData is true, // video data is copied to PlanarYCbCrImage. - static void SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, + static bool SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, const VideoInfo& aInfo, const YCbCrBuffer &aBuffer, const IntRect& aPicture, bool aCopyData); size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; // Dimensions at which to display the video frame. The picture region
--- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -740,16 +740,19 @@ MediaDecoder::NotifyDecodeError() AbstractThread::MainThread()->Dispatch(r.forget()); } void MediaDecoder::NotifyDataEnded(nsresult aStatus) { RefPtr<MediaDecoder> self = this; nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () { + if (self->mShuttingDown) { + return; + } self->NotifyDownloadEnded(aStatus); if (NS_SUCCEEDED(aStatus)) { HTMLMediaElement* element = self->mOwner->GetMediaElement(); if (element) { element->DownloadSuspended(); } // NotifySuspendedStatusChanged will tell the element that download // has been suspended "by the cache", which is true since we never
--- a/dom/media/VideoSegment.cpp +++ b/dom/media/VideoSegment.cpp @@ -75,17 +75,20 @@ VideoFrame::CreateBlackImage(const gfx:: data.mCrChannel = data.mCbChannel + aSize.height * data.mCbCrStride / 2; data.mCbCrSize = gfx::IntSize(aSize.width / 2, aSize.height / 2); data.mPicX = 0; data.mPicY = 0; data.mPicSize = gfx::IntSize(aSize.width, aSize.height); data.mStereoMode = StereoMode::MONO; // SetData copies data, so we can free data. - planar->SetData(data); + if (!planar->SetData(data)) { + MOZ_ASSERT(false); + return nullptr; + } return image.forget(); } #endif // !defined(MOZILLA_XPCOMRT_API) VideoChunk::VideoChunk() {}
--- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -53,16 +53,22 @@ if (SpecialPowers.Services.appinfo.name // Used by test_bug654550.html, for videoStats preference var gVideoTests = [ { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266 }, { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 }, { name:"bogus.duh", type:"bogus/duh" } ]; +// Temp hack for trackIDs and captureStream() -- bug 1215769 +var gLongerTests = [ + { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 }, + { name:"gizmo.mp4", type:"video/mp4", width:560, height:320, duration:5.56 }, +]; + // Used by test_progress to ensure we get the correct progress information // during resource download. var gProgressTests = [ { name:"r11025_u8_c1.wav", type:"audio/x-wav", duration:1.0, size:11069 }, { name:"big.wav", type:"audio/x-wav", duration:9.278981, size:102444 }, { name:"seek.ogv", type:"video/ogg", duration:3.966, size:285310 }, { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266, size:28942 }, { name:"seek.webm", type:"video/webm", duration:3.966, size:215529 },
--- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html +++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html @@ -9,17 +9,17 @@ <script type="application/javascript;version=1.8"> var manager = new MediaTestManager; createHTML({ bug: "1081409", title: "Captured video-only over peer connection", visible: true }).then(() => new Promise(resolve => { - manager.runTests(getPlayableVideos(gSmallTests), startTest); + manager.runTests(getPlayableVideos(gLongerTests), startTest); manager.onFinished = () => { // Tear down before SimpleTest.finish. if ("nsINetworkInterfaceListService" in SpecialPowers.Ci) { getNetworkUtils().tearDownNetwork(); } resolve(); }; }))
--- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -245,20 +245,26 @@ MediaEngineDefaultVideoSource::Notify(ns #ifdef MOZ_WEBRTC uint64_t timestamp = PR_Now(); YuvStamper::Encode(mOpts.mWidth, mOpts.mHeight, mOpts.mWidth, data.mYChannel, reinterpret_cast<unsigned char*>(×tamp), sizeof(timestamp), 0, 0); #endif - ycbcr_image->SetData(data); + bool setData = ycbcr_image->SetData(data); + MOZ_ASSERT(setData); + // SetData copies data, so we can free the frame ReleaseFrame(data); + if (!setData) { + return NS_ERROR_FAILURE; + } + MonitorAutoLock lock(mMonitor); // implicitly releases last image mImage = ycbcr_image.forget(); return NS_OK; }
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -310,17 +310,20 @@ MediaEngineRemoteVideoSource::DeliverFra data.mCbChannel = frame + mHeight * data.mYStride; data.mCrChannel = data.mCbChannel + ((mHeight+1)/2) * data.mCbCrStride; data.mCbCrSize = IntSize((mWidth+1)/ 2, (mHeight+1)/ 2); data.mPicX = 0; data.mPicY = 0; data.mPicSize = IntSize(mWidth, mHeight); data.mStereoMode = StereoMode::MONO; - videoImage->SetData(data); + if (!videoImage->SetData(data)) { + MOZ_ASSERT(false); + return 0; + } #ifdef DEBUG static uint32_t frame_num = 0; LOGFRAME(("frame %d (%dx%d); timestamp %u, ntp_time %" PRIu64 ", render_time %" PRIu64, frame_num++, mWidth, mHeight, time_stamp, ntp_time, render_time)); #endif // we don't touch anything in 'this' until here (except for snapshot,
--- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -1439,59 +1439,35 @@ PluginInstanceParent::NPP_Print(NPPrint* } PPluginScriptableObjectParent* PluginInstanceParent::AllocPPluginScriptableObjectParent() { return new PluginScriptableObjectParent(Proxy); } -#ifdef DEBUG -namespace { - -struct ActorSearchData -{ - PluginScriptableObjectParent* actor; - bool found; -}; - -PLDHashOperator -ActorSearch(NPObject* aKey, - PluginScriptableObjectParent* aData, - void* aUserData) -{ - ActorSearchData* asd = reinterpret_cast<ActorSearchData*>(aUserData); - if (asd->actor == aData) { - asd->found = true; - return PL_DHASH_STOP; - } - return PL_DHASH_NEXT; -} - -} // namespace -#endif // DEBUG - bool PluginInstanceParent::DeallocPPluginScriptableObjectParent( PPluginScriptableObjectParent* aObject) { PluginScriptableObjectParent* actor = static_cast<PluginScriptableObjectParent*>(aObject); NPObject* object = actor->GetObject(false); if (object) { NS_ASSERTION(mScriptableObjects.Get(object, nullptr), "NPObject not in the hash!"); mScriptableObjects.Remove(object); } #ifdef DEBUG else { - ActorSearchData asd = { actor, false }; - mScriptableObjects.EnumerateRead(ActorSearch, &asd); - NS_ASSERTION(!asd.found, "Actor in the hash with a null NPObject!"); + for (auto iter = mScriptableObjects.Iter(); !iter.Done(); iter.Next()) { + NS_ASSERTION(actor != iter.UserData(), + "Actor in the hash with a null NPObject!"); + } } #endif delete actor; return true; } bool
--- a/dom/presentation/interfaces/nsITCPPresentationServer.idl +++ b/dom/presentation/interfaces/nsITCPPresentationServer.idl @@ -17,26 +17,27 @@ interface nsIPresentationControlChannel; [scriptable, uuid(296fd171-e4d0-4de0-99ff-ad8ed52ddef3)] interface nsITCPDeviceInfo: nsISupports { readonly attribute AUTF8String id; readonly attribute AUTF8String address; readonly attribute uint16_t port; }; -[scriptable, uuid(fbb890a9-9e95-47d1-a425-86fd95881d81)] +[scriptable, uuid(09bddfaf-fcc2-4dc9-b33e-a509a1c2fb6d)] interface nsITCPPresentationServerListener: nsISupports { /** - * Callback while the server socket stops listening. - * @param aReason - * The reason of the socket close. NS_OK for manually |close|. - * <other-error> on failure. + * Callback while the server socket changes port. + * This event won't be cached so you should get current port after setting + * this listener to make sure the value is updated. + * @param aPort + * The port of the server socket. */ - void onClose(in nsresult aReason); + void onPortChange(in uint16_t aPort); /** * Callback while the remote host is requesting to start a presentation session. * @param aDeviceInfo The device information related to the remote host. * @param aUrl The URL requested to open by remote device. * @param aPresentationId The Id for representing this session. * @param aControlChannel The control channel for this session. */
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp +++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp @@ -8,38 +8,35 @@ #include "mozilla/Logging.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "nsAutoPtr.h" #include "nsComponentManagerUtils.h" #include "nsIObserverService.h" #include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" #ifdef MOZ_WIDGET_ANDROID #include "nsIPropertyBag2.h" #endif // MOZ_WIDGET_ANDROID #define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled" #define PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS "dom.presentation.discovery.timeout_ms" #define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable" #define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name" #define SERVICE_TYPE "_mozilla_papi._tcp." -inline static PRLogModuleInfo* -GetProviderLog() -{ - static PRLogModuleInfo* log = PR_NewLogModule("MulticastDNSDeviceProvider"); - return log; -} +static mozilla::LazyLogModule sMulticastDNSProviderLogModule("MulticastDNSDeviceProvider"); + #undef LOG_I -#define LOG_I(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Debug, (__VA_ARGS__)) +#define LOG_I(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Debug, (__VA_ARGS__)) #undef LOG_E -#define LOG_E(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Error, (__VA_ARGS__)) +#define LOG_E(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Error, (__VA_ARGS__)) namespace mozilla { namespace dom { namespace presentation { static const char* kObservedPrefs[] = { PREF_PRESENTATION_DISCOVERY, PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS, @@ -256,31 +253,46 @@ MulticastDNSDeviceProvider::RegisterServ { LOG_I("RegisterService: %s (%d)", mServiceName.get(), mDiscoverable); MOZ_ASSERT(NS_IsMainThread()); if (!mDiscoverable) { return NS_OK; } - MOZ_ASSERT(!mRegisterRequest); + nsresult rv; - nsresult rv; - if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) { - return rv; - } - if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->StartService(0)))) { - return rv; - } uint16_t servicePort; if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) { return rv; } /** + * If |servicePort| is non-zero, it means PresentationServer is running. + * Otherwise, we should make it start serving. + */ + if (!servicePort) { + if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) { + return rv; + } + if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->StartService(0)))) { + return rv; + } + if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) { + return rv; + } + } + + // Cancel on going service registration. + if (mRegisterRequest) { + mRegisterRequest->Cancel(NS_OK); + mRegisterRequest = nullptr; + } + + /** * Register the presentation control channel server as an mDNS service. */ nsCOMPtr<nsIDNSServiceInfo> serviceInfo = do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType( @@ -750,22 +762,19 @@ NS_IMETHODIMP MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode) { LOG_E("OnRegistrationFailed: %d", aErrorCode); MOZ_ASSERT(NS_IsMainThread()); mRegisterRequest = nullptr; - nsresult rv; - if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) { - if (NS_WARN_IF(NS_FAILED(rv = RegisterService()))) { - return rv; - } + return NS_DispatchToMainThread( + NS_NewRunnableMethod(this, &MulticastDNSDeviceProvider::RegisterService)); } return NS_OK; } NS_IMETHODIMP MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode) @@ -850,27 +859,23 @@ MulticastDNSDeviceProvider::OnResolveFai LOG_E("OnResolveFailed: %d", aErrorCode); MOZ_ASSERT(NS_IsMainThread()); return NS_OK; } // nsITCPPresentationServerListener NS_IMETHODIMP -MulticastDNSDeviceProvider::OnClose(nsresult aReason) +MulticastDNSDeviceProvider::OnPortChange(uint16_t aPort) { - LOG_I("OnClose: %x", aReason); + LOG_I("OnPortChange: %d", aPort); MOZ_ASSERT(NS_IsMainThread()); - UnregisterService(aReason); - - nsresult rv; - - if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) { - return rv; + if (mDiscoverable) { + RegisterService(); } return NS_OK; } NS_IMETHODIMP MulticastDNSDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo, const nsAString& aUrl,
--- a/dom/presentation/provider/TCPPresentationServer.js +++ b/dom/presentation/provider/TCPPresentationServer.js @@ -35,26 +35,21 @@ TCPPresentationServer.prototype = { _controlChannels: [], startService: function(aPort) { if (this._isServiceInit()) { DEBUG && log("TCPPresentationServer - server socket has been initialized"); throw Cr.NS_ERROR_FAILURE; } - if (typeof aPort === "undefined") { - DEBUG && log("TCPPresentationServer - aPort should not be undefined"); - throw Cr.NS_ERROR_FAILURE; - } - /** * 0 or undefined indicates opt-out parameter, and a port will be selected * automatically. */ - let serverSocketPort = (aPort !== 0) ? aPort : -1; + let serverSocketPort = (typeof aPort !== "undefined" && aPort !== 0) ? aPort : -1; this._serverSocket = Cc["@mozilla.org/network/server-socket;1"] .createInstance(Ci.nsIServerSocket); if (!this._serverSocket) { DEBUG && log("TCPPresentationServer - create server socket fail."); throw Cr.NS_ERROR_FAILURE; } @@ -65,17 +60,22 @@ TCPPresentationServer.prototype = { } catch (e) { // NS_ERROR_SOCKET_ADDRESS_IN_USE DEBUG && log("TCPPresentationServer - init server socket fail: " + e); throw Cr.NS_ERROR_FAILURE; } this._port = this._serverSocket.port; - DEBUG && log("TCPPresentationServer - service start on port: " + aPort); + DEBUG && log("TCPPresentationServer - service start on port: " + this._port); + + // Monitor network interface change to restart server socket. + // Only B2G has nsINetworkManager + Services.obs.addObserver(this, "network-active-changed", false); + Services.obs.addObserver(this, "network:offline-status-changed", false); }, get id() { return this._id; }, set id(aId) { this._id = aId; @@ -173,41 +173,84 @@ TCPPresentationServer.prototype = { if (index !== -1) { delete this._controlChannels[index]; } }, // nsIServerSocketListener (Triggered by nsIServerSocket.init) onStopListening: function(aServerSocket, aStatus) { DEBUG && log("TCPPresentationServer - onStopListening: " + aStatus); - - if (this._serverSocket) { - DEBUG && log("TCPPresentationServer - should be non-manually closed"); - this.close(); - } else if (aStatus === Cr.NS_BINDING_ABORTED) { - DEBUG && log("TCPPresentationServer - should be manually closed"); - aStatus = Cr.NS_OK; - } - - this._listener && this._listener.onClose(aStatus); }, close: function() { DEBUG && log("TCPPresentationServer - close"); - if (this._serverSocket) { + if (this._isServiceInit()) { DEBUG && log("TCPPresentationServer - close server socket"); this._serverSocket.close(); this._serverSocket = null; + + Services.obs.removeObserver(this, "network-active-changed"); + Services.obs.removeObserver(this, "network:offline-status-changed"); } this._port = 0; }, + // nsIObserver + observe: function(aSubject, aTopic, aData) { + DEBUG && log("TCPPresentationServer - observe: " + aTopic); + switch (aTopic) { + case "network-active-changed": { + if (!aSubject) { + DEBUG && log("No active network"); + return; + } + + /** + * Restart service only when original status is online because other + * cases will be handled by "network:offline-status-changed". + */ + if (!Services.io.offline) { + this._restartService(); + } + break; + } + case "network:offline-status-changed": { + if (aData == "offline") { + DEBUG && log("network offline"); + return; + } + this._restartService(); + break; + } + } + }, + + _restartService: function() { + DEBUG && log("TCPPresentationServer - restart service"); + + // restart server socket + if (this._isServiceInit()) { + let port = this._port; + this.close(); + + try { + this.startService(); + if (this._listener && this._port !== port) { + this._listener.onPortChange(this._port); + } + } catch (e) { + DEBUG && log("TCPPresentationServer - restart service fail: " + e); + } + } + }, + classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"), QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener, - Ci.nsITCPPresentationServer]), + Ci.nsITCPPresentationServer, + Ci.nsIObserver]), }; function ChannelDescription(aInit) { this._type = aInit.type; switch (this._type) { case Ci.nsIPresentationChannelDescription.TYPE_TCP: this._tcpAddresses = Cc["@mozilla.org/array;1"] .createInstance(Ci.nsIMutableArray);
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js +++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js @@ -997,17 +997,18 @@ function serverClosed() { Assert.equal(listener.devices.length, 0); provider.listener = listener; Assert.equal(mockObj.serviceRegistered, 1); Assert.equal(mockObj.serviceUnregistered, 0); Assert.equal(listener.devices.length, 1); let serverListener = provider.QueryInterface(Ci.nsITCPPresentationServerListener); - serverListener.onClose(Cr.NS_ERROR_UNEXPECTED); + let randomPort = 9527; + serverListener.onPortChange(randomPort); Assert.equal(mockObj.serviceRegistered, 2); Assert.equal(mockObj.serviceUnregistered, 1); Assert.equal(listener.devices.length, 1); // Unregister provider.listener = null; Assert.equal(mockObj.serviceRegistered, 2);
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js +++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js @@ -178,49 +178,50 @@ function testPresentationServer() { Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed'); yayFuncs.presenterControlChannelClose(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function setOffline() { - let expectedReason; tps.listener = { - onClose: function(aReason) { - Assert.equal(aReason, Cr.NS_ERROR_ABORT, 'TCPPresentationServer close as expected'); - Services.io.offline = false; + onPortChange: function(aPort) { + Assert.notEqual(aPort, 0, 'TCPPresentationServer port changed and the port should be valid'); + tps.close(); run_next_test(); }, - } + }; - // Let the server socket be closed non-manually + // Let the server socket restart automatically. Services.io.offline = true; + Services.io.offline = false; } function oneMoreLoop() { try { tps.startService(PRESENTER_CONTROL_CHANNEL_PORT); testPresentationServer(); } catch (e) { Assert.ok(false, 'TCP presentation init fail:' + e); run_next_test(); } } function shutdown() { tps.listener = { - onClose: function(aReason) { - Assert.equal(aReason, Cr.NS_OK, 'TCPPresentationServer close success'); - run_next_test(); + onPortChange: function(aPort) { + Assert.ok(false, 'TCPPresentationServer port changed'); }, - } + }; tps.close(); + Assert.equal(tps.port, 0, "TCPPresentationServer closed"); + run_next_test(); } // Test manually close control channel with NS_ERROR_FAILURE function changeCloseReason() { CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE; run_next_test(); }
--- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -108,20 +108,17 @@ namespace { void AsyncLog(nsIInterceptedChannel *aInterceptedChannel, const nsACString& aRespondWithScriptSpec, uint32_t aRespondWithLineNumber, uint32_t aRespondWithColumnNumber, const nsACString& aMessageName, const nsTArray<nsString>& aParams) { MOZ_ASSERT(aInterceptedChannel); - // Since the intercepted channel is kept alive and paused while handling - // the FetchEvent, we are guaranteed the reporter is stable on the worker - // thread. - nsIConsoleReportCollector* reporter = + nsCOMPtr<nsIConsoleReportCollector> reporter = aInterceptedChannel->GetConsoleReportCollector(); if (reporter) { reporter->AddConsoleReport(nsIScriptError::errorFlag, NS_LITERAL_CSTRING("Service Worker Interception"), nsContentUtils::eDOM_PROPERTIES, aRespondWithScriptSpec, aRespondWithLineNumber, aRespondWithColumnNumber,
--- a/embedding/components/commandhandler/nsCommandGroup.cpp +++ b/embedding/components/commandhandler/nsCommandGroup.cpp @@ -22,24 +22,22 @@ public: nsControllerCommandGroup::GroupsHashtable& aInHashTable); NS_DECL_ISUPPORTS NS_DECL_NSISIMPLEENUMERATOR protected: virtual ~nsGroupsEnumerator(); - static PLDHashOperator HashEnum(const nsACString& aKey, - nsTArray<nsCString>* aData, void* aClosure); nsresult Initialize(); protected: nsControllerCommandGroup::GroupsHashtable& mHashTable; int32_t mIndex; - char** mGroupNames; // array of pointers to char16_t* in the hash table + const char** mGroupNames; // array of pointers to char16_t* in the hash table bool mInitted; }; /* Implementation file */ NS_IMPL_ISUPPORTS(nsGroupsEnumerator, nsISimpleEnumerator) nsGroupsEnumerator::nsGroupsEnumerator( nsControllerCommandGroup::GroupsHashtable& aInHashTable) @@ -87,54 +85,45 @@ nsGroupsEnumerator::GetNext(nsISupports* } } mIndex++; if (mIndex >= static_cast<int32_t>(mHashTable.Count())) { return NS_ERROR_FAILURE; } - char* thisGroupName = mGroupNames[mIndex]; + const char* thisGroupName = mGroupNames[mIndex]; nsCOMPtr<nsISupportsCString> supportsString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); if (NS_FAILED(rv)) { return rv; } supportsString->SetData(nsDependentCString(thisGroupName)); return CallQueryInterface(supportsString, aResult); } -/* static */ -/* return false to stop */ -PLDHashOperator -nsGroupsEnumerator::HashEnum(const nsACString& aKey, nsTArray<nsCString>* aData, - void* aClosure) -{ - nsGroupsEnumerator* groupsEnum = static_cast<nsGroupsEnumerator*>(aClosure); - groupsEnum->mGroupNames[groupsEnum->mIndex] = (char*)aKey.Data(); - groupsEnum->mIndex++; - return PL_DHASH_NEXT; -} - nsresult nsGroupsEnumerator::Initialize() { if (mInitted) { return NS_OK; } - mGroupNames = new char*[mHashTable.Count()]; + mGroupNames = new const char*[mHashTable.Count()]; if (!mGroupNames) { return NS_ERROR_OUT_OF_MEMORY; } mIndex = 0; - mHashTable.EnumerateRead(HashEnum, this); + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + mGroupNames[mIndex] = iter.Key().Data(); + mIndex++; + } mIndex = -1; mInitted = true; return NS_OK; } class nsNamedGroupEnumerator : public nsISimpleEnumerator {
--- a/embedding/components/commandhandler/nsCommandManager.cpp +++ b/embedding/components/commandhandler/nsCommandManager.cpp @@ -29,39 +29,29 @@ nsCommandManager::nsCommandManager() : mWindow(nullptr) { } nsCommandManager::~nsCommandManager() { } -static PLDHashOperator -TraverseCommandObservers(const char* aKey, - nsCommandManager::ObserverList* aObservers, - void* aClosure) -{ - nsCycleCollectionTraversalCallback* cb = - static_cast<nsCycleCollectionTraversalCallback*>(aClosure); - - int32_t i, numItems = aObservers->Length(); - for (i = 0; i < numItems; ++i) { - cb->NoteXPCOMChild(aObservers->ElementAt(i)); - } - - return PL_DHASH_NEXT; -} - NS_IMPL_CYCLE_COLLECTION_CLASS(nsCommandManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCommandManager) tmp->mObserversTable.Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCommandManager) - tmp->mObserversTable.EnumerateRead(TraverseCommandObservers, &cb); + for (auto iter = tmp->mObserversTable.Iter(); !iter.Done(); iter.Next()) { + nsCommandManager::ObserverList* observers = iter.UserData(); + int32_t numItems = observers->Length(); + for (int32_t i = 0; i < numItems; ++i) { + cb.NoteXPCOMChild(observers->ElementAt(i)); + } + } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCommandManager) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCommandManager) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCommandManager) NS_INTERFACE_MAP_ENTRY(nsICommandManager) NS_INTERFACE_MAP_ENTRY(nsPICommandUpdater)
--- a/embedding/components/commandhandler/nsControllerCommandTable.cpp +++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp @@ -173,38 +173,29 @@ nsControllerCommandTable::GetCommandStat NS_WARNING("Controller command table asked to do a command that it does " "not handle"); return NS_OK; } return commandHandler->GetCommandStateParams(aCommandName, aParams, aCommandRefCon); } -static PLDHashOperator -AddCommand(const nsACString& aKey, nsIControllerCommand* aData, void* aArg) -{ - // aArg is a pointer to a array of strings. It gets incremented after - // allocating each one so that it points to the next location for AddCommand - // to assign a string to. - char*** commands = static_cast<char***>(aArg); - (**commands) = ToNewCString(aKey); - (*commands)++; - return PL_DHASH_NEXT; -} - NS_IMETHODIMP nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, char*** aCommands) { char** commands = static_cast<char**>(moz_xmalloc(sizeof(char*) * mCommandsTable.Count())); *aCount = mCommandsTable.Count(); *aCommands = commands; - mCommandsTable.EnumerateRead(AddCommand, &commands); + for (auto iter = mCommandsTable.Iter(); !iter.Done(); iter.Next()) { + *commands = ToNewCString(iter.Key()); + commands++; + } return NS_OK; } nsresult NS_NewControllerCommandTable(nsIControllerCommandTable** aResult) { NS_PRECONDITION(aResult != nullptr, "null ptr"); if (!aResult) {
--- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -98,22 +98,16 @@ struct nsWebBrowserPersist::URIData nsCOMPtr<nsIURI> mDataPath; nsCOMPtr<nsIURI> mRelativeDocumentURI; nsCString mRelativePathToData; nsCString mCharset; nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut); }; -struct nsWebBrowserPersist::URIFixupData -{ - RefPtr<FlatURIMap> mFlatMap; - nsCOMPtr<nsIURI> mTargetBaseURI; -}; - // Information about the output stream struct nsWebBrowserPersist::OutputData { nsCOMPtr<nsIURI> mFile; nsCOMPtr<nsIURI> mOriginalLocation; nsCOMPtr<nsIOutputStream> mStream; int64_t mSelfProgress; int64_t mSelfProgressMax; @@ -588,23 +582,79 @@ nsWebBrowserPersist::SerializeNextFile() // First, handle gathered URIs. // Count how many URIs in the URI map require persisting uint32_t urisToPersist = 0; if (mURIMap.Count() > 0) { // This is potentially O(n^2), when taking into account the // number of times this method is called. If it becomes a // bottleneck, the count of not-yet-persisted URIs could be // maintained separately. - mURIMap.EnumerateRead(EnumCountURIsToPersist, &urisToPersist); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + if (data->mNeedsPersisting && !data->mSaved) { + urisToPersist++; + } + } } if (urisToPersist > 0) { // Persist each file in the uri map. The document(s) // will be saved after the last one of these is saved. - mURIMap.EnumerateRead(EnumPersistURIs, this); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + + if (!data->mNeedsPersisting || data->mSaved) { + continue; + } + + nsresult rv; + + // Create a URI from the key. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), iter.Key(), + data->mCharset.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // Make a URI to save the data to. + nsCOMPtr<nsIURI> fileAsURI; + rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = AppendPathToURI(fileAsURI, data->mFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // The Referrer Policy doesn't matter here since the referrer is + // nullptr. + rv = SaveURIInternal(uri, nullptr, nullptr, + mozilla::net::RP_Default, nullptr, nullptr, + fileAsURI, true, mIsPrivate); + // If SaveURIInternal fails, then it will have called EndDownload, + // which means that |data| is no longer valid memory. We MUST bail. + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (rv == NS_OK) { + // Store the actual object because once it's persisted this + // will be fixed up with the right file extension. + data->mFile = fileAsURI; + data->mSaved = true; + } else { + data->mNeedsFixup = false; + } + + if (mSerializingOutput) { + break; + } + } } // If there are downloads happening, wait until they're done; the // OnStopRequest handler will call this method again. if (mOutputMap.Count() > 0) { return; } @@ -647,27 +697,28 @@ nsWebBrowserPersist::SerializeNextFile() if (mTargetBaseURI) { rv = mTargetBaseURI->GetSpec(targetBaseSpec); if (NS_FAILED(rv)) { SendErrorStatusChange(true, rv, nullptr, nullptr); EndDownload(rv); return; } } - + // mFlatURIMap must be rebuilt each time through SerializeNextFile, as // mTargetBaseURI is used to create the relative URLs and will be different // with each serialized document. RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec); - - URIFixupData fixupData; - fixupData.mFlatMap = flatMap; - fixupData.mTargetBaseURI = mTargetBaseURI; - - mURIMap.EnumerateRead(EnumCopyURIsToFlatMap, &fixupData); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString mapTo; + nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo); + if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { + flatMap->Add(iter.Key(), mapTo); + } + } mFlatURIMap = flatMap.forget(); nsCOMPtr<nsIFile> localFile; GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile)); if (localFile) { // if we're not replacing an existing file but the file // exists, something is wrong bool fileExists = false; @@ -1750,33 +1801,45 @@ nsWebBrowserPersist::FinishSaveDocumentI // Done walking DOMs; on to the serialization phase. SerializeNextFile(); } } void nsWebBrowserPersist::Cleanup() { mURIMap.Clear(); - mOutputMap.EnumerateRead(EnumCleanupOutputMap, this); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } mOutputMap.Clear(); - mUploadList.EnumerateRead(EnumCleanupUploadList, this); + + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } mUploadList.Clear(); + uint32_t i; - for (i = 0; i < mDocList.Length(); i++) - { + for (i = 0; i < mDocList.Length(); i++) { DocData *docData = mDocList.ElementAt(i); delete docData; } mDocList.Clear(); - for (i = 0; i < mCleanupList.Length(); i++) - { + + for (i = 0; i < mCleanupList.Length(); i++) { CleanupData *cleanupData = mCleanupList.ElementAt(i); delete cleanupData; } mCleanupList.Clear(); + mFilenameList.Clear(); } void nsWebBrowserPersist::CleanupLocalFiles() { // Two passes, the first pass cleans up files, the second pass tests // for and then deletes empty directories. Directories that are not // empty after the first pass must contain files from something else @@ -2314,233 +2377,93 @@ nsWebBrowserPersist::EndDownload(nsresul mCompleted = true; Cleanup(); mProgressListener = nullptr; mProgressListener2 = nullptr; mEventSink = nullptr; } -struct MOZ_STACK_CLASS FixRedirectData -{ - nsCOMPtr<nsIChannel> mNewChannel; - nsCOMPtr<nsIURI> mOriginalURI; - nsCOMPtr<nsISupports> mMatchingKey; -}; - nsresult nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel) { NS_ENSURE_ARG_POINTER(aNewChannel); + + // Iterate through existing open channels looking for one with a URI + // matching the one specified. nsCOMPtr<nsIURI> originalURI; - - // Enumerate through existing open channels looking for one with - // a URI matching the one specified. - - FixRedirectData data; - data.mNewChannel = aNewChannel; - data.mNewChannel->GetOriginalURI(getter_AddRefs(data.mOriginalURI)); - mOutputMap.EnumerateRead(EnumFixRedirect, &data); - - // If a match is found, remove the data entry with the old channel key - // and re-add it with the new channel key. - - if (data.mMatchingKey) - { - nsAutoPtr<OutputData> outputData; - mOutputMap.RemoveAndForget(data.mMatchingKey, outputData); - NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); - - // Store data again with new channel unless told to ignore redirects - if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) - { - nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel); - mOutputMap.Put(keyPtr, outputData.forget()); + aNewChannel->GetOriginalURI(getter_AddRefs(originalURI)); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Key(); + nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key); + nsCOMPtr<nsIURI> thisURI; + + thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); + + // Compare this channel's URI to the one passed in. + bool matchingURI = false; + thisURI->Equals(originalURI, &matchingURI); + if (matchingURI) { + // If a match is found, remove the data entry with the old channel + // key and re-add it with the new channel key. + nsAutoPtr<OutputData> outputData; + mOutputMap.RemoveAndForget(key, outputData); + NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); + + // Store data again with new channel unless told to ignore redirects + if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) { + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel); + mOutputMap.Put(keyPtr, outputData.forget()); + } + + break; } } - return NS_OK; } -PLDHashOperator -nsWebBrowserPersist::EnumFixRedirect(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - FixRedirectData *data = static_cast<FixRedirectData*>(aClosure); - - nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(aKey); - nsCOMPtr<nsIURI> thisURI; - - thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); - - // Compare this channel's URI to the one passed in. - bool matchingURI = false; - thisURI->Equals(data->mOriginalURI, &matchingURI); - if (matchingURI) - { - data->mMatchingKey = aKey; - return PL_DHASH_STOP; - } - - return PL_DHASH_NEXT; -} - void nsWebBrowserPersist::CalcTotalProgress() { mTotalCurrentProgress = 0; mTotalMaxProgress = 0; - if (mOutputMap.Count() > 0) - { + if (mOutputMap.Count() > 0) { // Total up the progress of each output stream - mOutputMap.EnumerateRead(EnumCalcProgress, this); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + // Only count toward total progress if destination file is local. + OutputData* data = iter.UserData(); + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile); + if (fileURL) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } } - if (mUploadList.Count() > 0) - { + if (mUploadList.Count() > 0) { // Total up the progress of each upload - mUploadList.EnumerateRead(EnumCalcUploadProgress, this); + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + UploadData* data = iter.UserData(); + if (data) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } } // XXX this code seems pretty bogus and pointless if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) { // No output streams so we must be complete mTotalCurrentProgress = 10000; mTotalMaxProgress = 10000; } } -PLDHashOperator -nsWebBrowserPersist::EnumCalcProgress(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - nsWebBrowserPersist *pthis = static_cast<nsWebBrowserPersist*>(aClosure); - - // only count toward total progress if destination file is local - nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aData->mFile); - if (fileURL) - { - pthis->mTotalCurrentProgress += aData->mSelfProgress; - pthis->mTotalMaxProgress += aData->mSelfProgressMax; - } - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData, void* aClosure) -{ - if (aData && aClosure) - { - nsWebBrowserPersist *pthis = static_cast<nsWebBrowserPersist*>(aClosure); - pthis->mTotalCurrentProgress += aData->mSelfProgress; - pthis->mTotalMaxProgress += aData->mSelfProgressMax; - } - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumCountURIsToPersist(const nsACString &aKey, URIData *aData, void* aClosure) -{ - uint32_t *count = static_cast<uint32_t*>(aClosure); - if (aData->mNeedsPersisting && !aData->mSaved) - { - (*count)++; - } - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumPersistURIs(const nsACString &aKey, URIData *aData, void* aClosure) -{ - if (!aData->mNeedsPersisting || aData->mSaved) - { - return PL_DHASH_NEXT; - } - - nsWebBrowserPersist *pthis = static_cast<nsWebBrowserPersist*>(aClosure); - nsresult rv; - - // Create a URI from the key - nsAutoCString key = nsAutoCString(aKey); - nsCOMPtr<nsIURI> uri; - rv = NS_NewURI(getter_AddRefs(uri), - nsDependentCString(key.get(), key.Length()), - aData->mCharset.get()); - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - - // Make a URI to save the data to - nsCOMPtr<nsIURI> fileAsURI; - rv = aData->mDataPath->Clone(getter_AddRefs(fileAsURI)); - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - rv = pthis->AppendPathToURI(fileAsURI, aData->mFilename); - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - - // The Referrer Policy doesn't matter here since the referrer is nullptr. - rv = pthis->SaveURIInternal(uri, nullptr, nullptr, mozilla::net::RP_Default, - nullptr, nullptr, fileAsURI, true, pthis->mIsPrivate); - // if SaveURIInternal fails, then it will have called EndDownload, - // which means that |aData| is no longer valid memory. we MUST bail. - NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); - - if (rv == NS_OK) - { - // Store the actual object because once it's persisted this - // will be fixed up with the right file extension. - - aData->mFile = fileAsURI; - aData->mSaved = true; - } - else - { - aData->mNeedsFixup = false; - } - - if (pthis->mSerializingOutput) - return PL_DHASH_STOP; - - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumCleanupOutputMap(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - nsCOMPtr<nsIChannel> channel = do_QueryInterface(aKey); - if (channel) - { - channel->Cancel(NS_BINDING_ABORTED); - } - return PL_DHASH_NEXT; -} - -PLDHashOperator -nsWebBrowserPersist::EnumCleanupUploadList(nsISupports *aKey, UploadData *aData, void* aClosure) -{ - nsCOMPtr<nsIChannel> channel = do_QueryInterface(aKey); - if (channel) - { - channel->Cancel(NS_BINDING_ABORTED); - } - return PL_DHASH_NEXT; -} - -/* static */ PLDHashOperator -nsWebBrowserPersist::EnumCopyURIsToFlatMap(const nsACString &aKey, - URIData *aData, - void* aClosure) -{ - URIFixupData *fixupData = static_cast<URIFixupData*>(aClosure); - FlatURIMap* theMap = fixupData->mFlatMap; - nsAutoCString mapTo; - nsresult rv = aData->GetLocalURI(fixupData->mTargetBaseURI, mapTo); - if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { - theMap->Add(aKey, mapTo); - } - return PL_DHASH_NEXT; -} - nsresult nsWebBrowserPersist::StoreURI( const char *aURI, bool aNeedsPersisting, URIData **aData) { NS_ENSURE_ARG_POINTER(aURI); nsCOMPtr<nsIURI> uri; nsresult rv = NS_NewURI(getter_AddRefs(uri),
--- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -131,34 +131,16 @@ private: void EndDownload(nsresult aResult); void FinishDownload(); void SerializeNextFile(); void CalcTotalProgress(); void SetApplyConversionIfNeeded(nsIChannel *aChannel); - // Hash table enumerators - static PLDHashOperator EnumPersistURIs( - const nsACString &aKey, URIData *aData, void* aClosure); - static PLDHashOperator EnumCleanupOutputMap( - nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCleanupUploadList( - nsISupports *aKey, UploadData *aData, void* aClosure); - static PLDHashOperator EnumCalcProgress( - nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCalcUploadProgress( - nsISupports *aKey, UploadData *aData, void* aClosure); - static PLDHashOperator EnumFixRedirect( - nsISupports *aKey, OutputData *aData, void* aClosure); - static PLDHashOperator EnumCountURIsToPersist( - const nsACString &aKey, URIData *aData, void* aClosure); - static PLDHashOperator EnumCopyURIsToFlatMap( - const nsACString &aKey, URIData *aData, void* aClosure); - nsCOMPtr<nsIURI> mCurrentDataPath; bool mCurrentDataPathIsRelative; nsCString mCurrentRelativePathToData; nsCOMPtr<nsIURI> mCurrentBaseURI; nsCString mCurrentCharset; nsCOMPtr<nsIURI> mTargetBaseURI; uint32_t mCurrentThingsToPersist;
--- a/gfx/layers/GrallocImages.cpp +++ b/gfx/layers/GrallocImages.cpp @@ -52,30 +52,30 @@ GrallocImage::GrallocImage() { mFormat = ImageFormat::GRALLOC_PLANAR_YCBCR; } GrallocImage::~GrallocImage() { } -void +bool GrallocImage::SetData(const Data& aData) { MOZ_ASSERT(!mTextureClient, "TextureClient is already set"); NS_PRECONDITION(aData.mYSize.width % 2 == 0, "Image should have even width"); NS_PRECONDITION(aData.mYSize.height % 2 == 0, "Image should have even height"); NS_PRECONDITION(aData.mYStride % 16 == 0, "Image should have stride of multiple of 16 pixels"); mData = aData; mSize = aData.mPicSize; if (gfxPlatform::GetPlatform()->IsInGonkEmulator()) { // Emulator does not support HAL_PIXEL_FORMAT_YV12. - return; + return false; } RefPtr<GrallocTextureClientOGL> textureClient = new GrallocTextureClientOGL(ImageBridgeChild::GetSingleton(), gfx::SurfaceFormat::UNKNOWN, gfx::BackendType::NONE); // GrallocImages are all YUV and don't support alpha. textureClient->SetIsOpaque(true); @@ -83,25 +83,25 @@ GrallocImage::SetData(const Data& aData) textureClient->AllocateGralloc(mData.mYSize, HAL_PIXEL_FORMAT_YV12, GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_SW_WRITE_OFTEN | GraphicBuffer::USAGE_HW_TEXTURE); sp<GraphicBuffer> graphicBuffer = textureClient->GetGraphicBuffer(); if (!result || !graphicBuffer.get()) { mTextureClient = nullptr; - return; + return false; } mTextureClient = textureClient; void* vaddr; if (graphicBuffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &vaddr) != OK) { - return; + return false; } uint8_t* yChannel = static_cast<uint8_t*>(vaddr); gfx::IntSize ySize = aData.mYSize; int32_t yStride = graphicBuffer->getStride(); uint8_t* vChannel = yChannel + (yStride * ySize.height); gfx::IntSize uvSize = gfx::IntSize(ySize.width / 2, @@ -139,22 +139,24 @@ GrallocImage::SetData(const Data& aData) graphicBuffer->unlock(); // Initialze the channels' addresses. // Do not cache the addresses when gralloc buffer is not locked. // gralloc hal could map gralloc buffer only when the buffer is locked, // though some gralloc hals implementation maps it when it is allocated. mData.mYChannel = nullptr; mData.mCrChannel = nullptr; mData.mCbChannel = nullptr; + return true; } -void GrallocImage::SetData(const GrallocData& aData) +bool GrallocImage::SetData(const GrallocData& aData) { mTextureClient = static_cast<GrallocTextureClientOGL*>(aData.mGraphicBuffer.get()); mSize = aData.mPicSize; + return true; } /** * Converts YVU420 semi planar frames to RGB565, possibly taking different * stride values. * Needed because the Android ColorConverter class assumes that the Y and UV * channels have equal stride. */
--- a/gfx/layers/GrallocImages.h +++ b/gfx/layers/GrallocImages.h @@ -61,23 +61,23 @@ public: GrallocImage(); virtual ~GrallocImage(); /** * This makes a copy of the data buffers, in order to support functioning * in all different layer managers. */ - virtual void SetData(const Data& aData); + virtual bool SetData(const Data& aData); /** * Share the SurfaceDescriptor without making the copy, in order * to support functioning in all different layer managers. */ - virtual void SetData(const GrallocData& aData); + virtual bool SetData(const GrallocData& aData); // From [android 4.0.4]/hardware/msm7k/libgralloc-qsd8k/gralloc_priv.h enum { /* OEM specific HAL formats */ HAL_PIXEL_FORMAT_YCbCr_422_P = 0x102, HAL_PIXEL_FORMAT_YCbCr_420_P = 0x103, HAL_PIXEL_FORMAT_YCbCr_420_SP = 0x109, HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO = 0x10A,
--- a/gfx/layers/ImageContainer.cpp +++ b/gfx/layers/ImageContainer.cpp @@ -479,66 +479,68 @@ CopyPlane(uint8_t *aDst, const uint8_t * src += aSkip; } aSrc += aStride; aDst += aStride; } } } -void +bool PlanarYCbCrImage::CopyData(const Data& aData) { mData = aData; // update buffer size size_t size = mData.mCbCrStride * mData.mCbCrSize.height * 2 + mData.mYStride * mData.mYSize.height; // get new buffer mBuffer = AllocateBuffer(size); if (!mBuffer) - return; + return false; // update buffer size mBufferSize = size; mData.mYChannel = mBuffer; mData.mCbChannel = mData.mYChannel + mData.mYStride * mData.mYSize.height; mData.mCrChannel = mData.mCbChannel + mData.mCbCrStride * mData.mCbCrSize.height; CopyPlane(mData.mYChannel, aData.mYChannel, mData.mYSize, mData.mYStride, mData.mYSkip); CopyPlane(mData.mCbChannel, aData.mCbChannel, mData.mCbCrSize, mData.mCbCrStride, mData.mCbSkip); CopyPlane(mData.mCrChannel, aData.mCrChannel, mData.mCbCrSize, mData.mCbCrStride, mData.mCrSkip); mSize = aData.mPicSize; + return true; } -void +bool PlanarYCbCrImage::SetData(const Data &aData) { - CopyData(aData); + return CopyData(aData); } gfxImageFormat PlanarYCbCrImage::GetOffscreenFormat() { return mOffscreenFormat == gfxImageFormat::Unknown ? gfxPlatform::GetPlatform()->GetOffscreenFormat() : mOffscreenFormat; } -void +bool PlanarYCbCrImage::SetDataNoCopy(const Data &aData) { mData = aData; mSize = aData.mPicSize; + return true; } uint8_t* PlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize) { // get new buffer mBuffer = AllocateBuffer(aSize); if (mBuffer) {
--- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -651,26 +651,26 @@ public: }; virtual ~PlanarYCbCrImage(); /** * This makes a copy of the data buffers, in order to support functioning * in all different layer managers. */ - virtual void SetData(const Data& aData); + virtual bool SetData(const Data& aData); /** * This doesn't make a copy of the data buffers. Can be used when mBuffer is * pre allocated with AllocateAndGetNewBuffer(size) and then SetDataNoCopy is * called to only update the picture size, planes etc. fields in mData. * The GStreamer media backend uses this to decode into PlanarYCbCrImage(s) * directly. */ - virtual void SetDataNoCopy(const Data &aData); + virtual bool SetDataNoCopy(const Data &aData); /** * This allocates and returns a new buffer */ virtual uint8_t* AllocateAndGetNewBuffer(uint32_t aSize); /** * Ask this Image to not convert YUV to RGB during SetData, and make @@ -704,17 +704,17 @@ public: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; protected: /** * Make a copy of the YCbCr data into local storage. * * @param aData Input image data. */ - void CopyData(const Data& aData); + bool CopyData(const Data& aData); /** * Return a buffer to store image data in. * The default implementation returns memory that can * be freed wit delete[] */ virtual uint8_t* AllocateBuffer(uint32_t aSize);
--- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -1046,16 +1046,21 @@ APZCTreeManager::ProcessEvent(WidgetInpu return result; } nsEventStatus APZCTreeManager::ProcessMouseEvent(WidgetMouseEventBase& aEvent, ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutInputBlockId) { + MOZ_ASSERT(NS_IsMainThread()); + + // Note, we call this before having transformed the reference point. + UpdateWheelTransaction(aEvent); + MouseInput input(aEvent); input.mOrigin = ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y); nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); aEvent.refPoint.x = input.mOrigin.x; aEvent.refPoint.y = input.mOrigin.y; aEvent.mFlags.mHandledByAPZ = true;
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -3346,21 +3346,18 @@ AsyncPanZoomController::CurrentTouchBloc PanGestureBlockState* AsyncPanZoomController::CurrentPanGestureBlock() { return GetInputQueue()->CurrentPanGestureBlock(); } void -AsyncPanZoomController::ResetInputState() +AsyncPanZoomController::ResetTouchInputState() { - // This may be called during non-touch input blocks as well. We send - // a fake cancel touch event here but on the assumption that none of the - // code in GEL assumes a CurrentTouchBlock() MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0); RefPtr<GestureEventListener> listener = GetGestureEventListener(); if (listener) { listener->HandleInputEvent(cancel); } CancelAnimationAndGestureState(); }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -829,19 +829,19 @@ public: * Given the number of touch points in an input event and touch block they * belong to, check if the event can result in a panning/zooming behavior. * This is primarily used to figure out when to dispatch the pointercancel * event for the pointer events spec. */ bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints); /** - * Clear internal state relating to input handling. + * Clear internal state relating to touch input handling. */ - void ResetInputState(); + void ResetTouchInputState(); private: void CancelAnimationAndGestureState(); RefPtr<InputQueue> mInputQueue; TouchBlockState* CurrentTouchBlock(); bool HasReadyTouchBlock();
--- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -646,17 +646,19 @@ InputQueue::ProcessInputBlocks() { curBlock->GetTargetApzc().get()); RefPtr<AsyncPanZoomController> target = curBlock->GetTargetApzc(); // target may be null here if the initial target was unconfirmed and then // we later got a confirmed null target. in that case drop the events. if (!target) { curBlock->DropEvents(); } else if (curBlock->IsDefaultPrevented()) { curBlock->DropEvents(); - target->ResetInputState(); + if (curBlock->AsTouchBlock()) { + target->ResetTouchInputState(); + } } else { UpdateActiveApzc(curBlock->GetTargetApzc()); curBlock->HandleEvents(); } MOZ_ASSERT(!curBlock->HasEvents()); if (mInputBlockQueue.Length() == 1 && curBlock->MustStayActive()) { // Some types of blocks (e.g. touch blocks) accumulate events until the @@ -672,17 +674,17 @@ InputQueue::ProcessInputBlocks() { mInputBlockQueue.RemoveElementAt(0); } while (!mInputBlockQueue.IsEmpty()); } void InputQueue::UpdateActiveApzc(const RefPtr<AsyncPanZoomController>& aNewActive) { if (mLastActiveApzc && mLastActiveApzc != aNewActive && mTouchCounter.GetActiveTouchCount() > 0) { - mLastActiveApzc->ResetInputState(); + mLastActiveApzc->ResetTouchInputState(); } mLastActiveApzc = aNewActive; } void InputQueue::Clear() { APZThreadUtils::AssertOnControllerThread();
--- a/gfx/layers/basic/BasicImages.cpp +++ b/gfx/layers/basic/BasicImages.cpp @@ -43,17 +43,17 @@ public: { if (mDecodedBuffer) { // Right now this only happens if the Image was never drawn, otherwise // this will have been tossed away at surface destruction. mRecycleBin->RecycleBuffer(mDecodedBuffer.forget(), mSize.height * mStride); } } - virtual void SetData(const Data& aData) override; + virtual bool SetData(const Data& aData) override; virtual void SetDelayedConversion(bool aDelayed) override { mDelayedConversion = aDelayed; } already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override; virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -86,53 +86,55 @@ public: image = new BasicPlanarYCbCrImage(aScaleHint, gfxPlatform::GetPlatform()->GetOffscreenFormat(), aRecycleBin); return image.forget(); } return ImageFactory::CreateImage(aFormat, aScaleHint, aRecycleBin); } }; -void +bool BasicPlanarYCbCrImage::SetData(const Data& aData) { PlanarYCbCrImage::SetData(aData); if (mDelayedConversion) { - return; + return false; } // Do some sanity checks to prevent integer overflow if (aData.mYSize.width > PlanarYCbCrImage::MAX_DIMENSION || aData.mYSize.height > PlanarYCbCrImage::MAX_DIMENSION) { NS_ERROR("Illegal image source width or height"); - return; + return false; } gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat(GetOffscreenFormat()); gfx::IntSize size(mScaleHint); gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size); if (size.width > PlanarYCbCrImage::MAX_DIMENSION || size.height > PlanarYCbCrImage::MAX_DIMENSION) { NS_ERROR("Illegal image dest width or height"); - return; + return false; } gfxImageFormat iFormat = gfx::SurfaceFormatToImageFormat(format); mStride = gfxASurface::FormatStrideForWidth(iFormat, size.width); mDecodedBuffer = AllocateBuffer(size.height * mStride); if (!mDecodedBuffer) { // out of memory - return; + return false; } gfx::ConvertYCbCrToRGB(aData, format, size, mDecodedBuffer, mStride); SetOffscreenFormat(iFormat); mSize = size; + + return true; } already_AddRefed<gfx::SourceSurface> BasicPlanarYCbCrImage::GetAsSourceSurface() { NS_ASSERTION(NS_IsMainThread(), "Must be main thread"); if (mSourceSurface) {
--- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -1645,16 +1645,17 @@ CompositorParent::SetControllerForLayerT NewRunnableFunction(&UpdateControllerForLayersId, aLayersId, aController)); } /*static*/ APZCTreeManager* CompositorParent::GetAPZCTreeManager(uint64_t aLayersId) { + EnsureLayerTreeMapReady(); const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId); if (state && state->mParent) { return state->mParent->mApzcTreeManager; } return nullptr; } float
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp @@ -75,38 +75,39 @@ SharedPlanarYCbCrImage::GetAsSourceSurfa { if (!mTextureClient) { NS_WARNING("Can't get as surface"); return nullptr; } return PlanarYCbCrImage::GetAsSourceSurface(); } -void +bool SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData) { // If mTextureClient has not already been allocated (through Allocate(aData)) // allocate it. This code path is slower than the one used when Allocate has // been called since it will trigger a full copy. PlanarYCbCrData data = aData; if (!mTextureClient && !Allocate(data)) { - return; + return false; } MOZ_ASSERT(mTextureClient->AsTextureClientYCbCr()); if (!mTextureClient->Lock(OpenMode::OPEN_WRITE_ONLY)) { MOZ_ASSERT(false, "Failed to lock the texture."); - return; + return false; } TextureClientAutoUnlock unlock(mTextureClient); if (!mTextureClient->AsTextureClientYCbCr()->UpdateYCbCr(aData)) { MOZ_ASSERT(false, "Failed to copy YCbCr data into the TextureClient"); - return; + return false; } mTextureClient->MarkImmutable(); + return true; } // needs to be overriden because the parent class sets mBuffer which we // do not want to happen. uint8_t* SharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize) { MOZ_ASSERT(!mTextureClient, "This image already has allocated data"); @@ -126,22 +127,22 @@ SharedPlanarYCbCrImage::AllocateAndGetNe // update buffer size mBufferSize = size; YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize()); return serializer.GetData(); } -void +bool SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData) { MOZ_ASSERT(mTextureClient, "This Image should have already allocated data"); if (!mTextureClient) { - return; + return false; } mData = aData; mSize = aData.mPicSize; /* SetDataNoCopy is used to update YUV plane offsets without (re)allocating * memory previously allocated with AllocateAndGetNewBuffer(). * serializer.GetData() returns the address of the memory previously allocated * with AllocateAndGetNewBuffer(), that we subtract from the Y, Cb, Cr * channels to compute 0-based offsets to pass to InitializeBufferInfo. @@ -154,16 +155,17 @@ SharedPlanarYCbCrImage::SetDataNoCopy(co serializer.InitializeBufferInfo(yOffset, cbOffset, crOffset, aData.mYStride, aData.mCbCrStride, aData.mYSize, aData.mCbCrSize, aData.mStereoMode); + return true; } uint8_t* SharedPlanarYCbCrImage::AllocateBuffer(uint32_t aSize) { MOZ_ASSERT(!mTextureClient, "This image already has allocated data"); mTextureClient = TextureClient::CreateWithBufferSize(mCompositable->GetForwarder(),
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.h +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.h @@ -30,18 +30,18 @@ public: protected: ~SharedPlanarYCbCrImage(); public: virtual TextureClient* GetTextureClient(CompositableClient* aClient) override; virtual uint8_t* GetBuffer() override; virtual already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override; - virtual void SetData(const PlanarYCbCrData& aData) override; - virtual void SetDataNoCopy(const Data &aData) override; + virtual bool SetData(const PlanarYCbCrData& aData) override; + virtual bool SetDataNoCopy(const Data &aData) override; virtual bool Allocate(PlanarYCbCrData& aData); virtual uint8_t* AllocateBuffer(uint32_t aSize) override; // needs to be overriden because the parent class sets mBuffer which we // do not want to happen. virtual uint8_t* AllocateAndGetNewBuffer(uint32_t aSize) override; virtual bool IsValid() override;
--- a/gfx/src/nsCoord.h +++ b/gfx/src/nsCoord.h @@ -6,16 +6,17 @@ #ifndef NSCOORD_H #define NSCOORD_H #include "nsAlgorithm.h" #include "nscore.h" #include "nsMathUtils.h" #include <math.h> #include <float.h> +#include <stdlib.h> #include "nsDebug.h" #include <algorithm> /* * Basic type used for the geometry classes. * * Normally all coordinates are maintained in an app unit coordinate @@ -52,16 +53,32 @@ typedef int32_t nscoord; inline void VERIFY_COORD(nscoord aCoord) { #ifdef NS_COORD_IS_FLOAT NS_ASSERTION(floorf(aCoord) == aCoord, "Coords cannot have fractions"); #endif } +/** + * Divide aSpace by aN. Assign the resulting quotient to aQuotient and + * return the remainder. + */ +inline nscoord NSCoordDivRem(nscoord aSpace, size_t aN, nscoord* aQuotient) +{ +#ifdef NS_COORD_IS_FLOAT + *aQuotient = aSpace / aN; + return 0.0f; +#else + div_t result = div(aSpace, aN); + *aQuotient = nscoord(result.quot); + return nscoord(result.rem); +#endif +} + inline nscoord NSCoordMulDiv(nscoord aMult1, nscoord aMult2, nscoord aDiv) { #ifdef NS_COORD_IS_FLOAT return (aMult1 * aMult2 / aDiv); #else return (int64_t(aMult1) * int64_t(aMult2) / int64_t(aDiv)); #endif }
--- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -1292,17 +1292,17 @@ js::AsmJSFunctionToString(JSContext* cx, // asm.js functions can't be anonymous MOZ_ASSERT(fun->atom()); if (!out.append(fun->atom())) return nullptr; size_t nameEnd = begin + fun->atom()->length(); Rooted<JSFlatString*> src(cx, source->substring(cx, nameEnd, end)); - if (!AppendUseStrictSource(cx, fun, src, out)) + if (!src || !AppendUseStrictSource(cx, fun, src, out)) return nullptr; } else { Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end)); if (!src) return nullptr; if (!out.append(src)) return nullptr; }
--- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -1923,16 +1923,37 @@ NewUDateFormat(JSContext* cx, HandleObje uPattern = Char16ToUChar(patternChars.twoByteRange().start().get()); if (!uPattern) return nullptr; uPatternLength = u_strlen(uPattern); UErrorCode status = U_ZERO_ERROR; + if (!uTimeZone) { +#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) + // JS::ResetTimeZone() recomputes the JS language's LocalTZA value. It + // *should* also recreate ICU's default time zone (used for formatting + // when no time zone has been specified), but this operation is slow. + // Doing it eagerly introduces a perf regression -- see bug 1220693. + // Therefore we perform it lazily, responding to the value of a global + // atomic variable that records whether ICU's default time zone is + // accurate. Baroque, but it's the only way to get the job done. + // + // Beware: this is kosher *only* if every place using ICU's default + // time zone performs the atomic compare-exchange and possible + // recreation song and dance routine here. + if (js::DefaultTimeZoneStatus.compareExchange(IcuTimeZoneStatus::NeedsUpdate, + IcuTimeZoneStatus::Updating)) + { + icu::TimeZone::recreateDefault(); + } +#endif + } + // If building with ICU headers before 50.1, use UDAT_IGNORE instead of // UDAT_PATTERN. UDateFormat* df = udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength, uPattern, uPatternLength, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return nullptr;
--- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -788,17 +788,20 @@ ModuleObject::setEvaluated() setReservedSlot(EvaluatedSlot, TrueHandleValue); } /* static */ bool ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval) { RootedScript script(cx, self->script()); RootedModuleEnvironmentObject scope(cx, self->environment()); - MOZ_ASSERT(scope); + if (!scope) { + JS_ReportError(cx, "Module declarations have not yet been instantiated"); + return false; + } return Execute(cx, script, *scope, rval.address()); } /* static */ ModuleNamespaceObject* ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleArrayObject exports) { MOZ_ASSERT(!self->namespace_());
--- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -78,17 +78,17 @@ js::obj_propertyIsEnumerable(JSContext* unsigned attrs = GetShapeAttributes(obj, shape); args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0); return true; } } /* Step 1. */ RootedId idRoot(cx); - if (!ValueToId<CanGC>(cx, idValue, &idRoot)) + if (!ToPropertyKey(cx, idValue, &idRoot)) return false; /* Step 2. */ RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; /* Step 3. */ @@ -526,17 +526,17 @@ js::obj_hasOwnProperty(JSContext* cx, un { args.rval().setBoolean(!!prop); return true; } } /* Step 1. */ RootedId idRoot(cx); - if (!ValueToId<CanGC>(cx, idValue, &idRoot)) + if (!ToPropertyKey(cx, idValue, &idRoot)) return false; /* Step 2. */ RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; /* Step 3. */ @@ -769,17 +769,17 @@ js::obj_defineProperty(JSContext* cx, un { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. RootedObject obj(cx); if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) return false; RootedId id(cx); - if (!ValueToId<CanGC>(cx, args.get(1), &id)) + if (!ToPropertyKey(cx, args.get(1), &id)) return false; // Steps 4-5. Rooted<PropertyDescriptor> desc(cx); if (!ToPropertyDescriptor(cx, args.get(2), true, &desc)) return false; // Steps 6-8.
--- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2486,18 +2486,19 @@ ASTSerializer::tryStatement(ParseNode* p bool ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst) { if (!pn) { dst.setMagic(JS_SERIALIZE_NO_NODE); return true; } - return (pn->isKind(PNK_VAR)) - ? variableDeclaration(pn, false, dst) + bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST); + return (lexical || pn->isKind(PNK_VAR)) + ? variableDeclaration(pn, lexical, dst) : expression(pn, dst); } bool ASTSerializer::forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt, MutableHandleValue dst) { RootedValue expr(cx); @@ -2636,42 +2637,41 @@ ASTSerializer::statement(ParseNode* pn, MOZ_ASSERT_IF(head->pn_kid1, head->pn_pos.encloses(head->pn_kid1->pn_pos)); MOZ_ASSERT_IF(head->pn_kid2, head->pn_pos.encloses(head->pn_kid2->pn_pos)); MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos)); RootedValue stmt(cx); if (!statement(pn->pn_right, &stmt)) return false; - if (head->isKind(PNK_FORIN)) { + if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) { RootedValue var(cx); - return (!head->pn_kid1 - ? pattern(head->pn_kid2, &var) - : head->pn_kid1->isKind(PNK_LEXICALSCOPE) - ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) - : variableDeclaration(head->pn_kid1, false, &var)) && - forIn(pn, head, var, stmt, dst); - } - - if (head->isKind(PNK_FOROF)) { - RootedValue var(cx); - return (!head->pn_kid1 - ? pattern(head->pn_kid2, &var) - : head->pn_kid1->isKind(PNK_LEXICALSCOPE) - ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) - : variableDeclaration(head->pn_kid1, false, &var)) && - forOf(pn, head, var, stmt, dst); + if (!head->pn_kid1) { + if (!pattern(head->pn_kid2, &var)) + return false; + } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) { + if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var)) + return false; + } else { + if (!variableDeclaration(head->pn_kid1, + head->pn_kid1->isKind(PNK_LET) || + head->pn_kid1->isKind(PNK_CONST), + &var)) + { + return false; + } + } + if (head->isKind(PNK_FORIN)) + return forIn(pn, head, var, stmt, dst); + return forOf(pn, head, var, stmt, dst); } RootedValue init(cx), test(cx), update(cx); - return forInit(head->pn_kid1 && !head->pn_kid1->isKind(PNK_FRESHENBLOCK) - ? head->pn_kid1 - : nullptr, - &init) && + return forInit(head->pn_kid1, &init) && optExpression(head->pn_kid2, &test) && optExpression(head->pn_kid3, &update) && builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst); } case PNK_BREAK: case PNK_CONTINUE: {
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2318,17 +2318,16 @@ BytecodeEmitter::checkSideEffects(ParseN case PNK_ARGSBODY: *answer = true; return true; case PNK_FORIN: // by PNK_FOR case PNK_FOROF: // by PNK_FOR case PNK_FORHEAD: // by PNK_FOR - case PNK_FRESHENBLOCK: // by PNK_FOR case PNK_CLASSMETHOD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS case PNK_CLASSMETHODLIST: // by PNK_CLASS case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT case PNK_IMPORT_SPEC: // by PNK_IMPORT case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT case PNK_EXPORT_SPEC: // by PNK_EXPORT @@ -5284,16 +5283,25 @@ BytecodeEmitter::emitIterator() return false; checkTypeSet(JSOP_CALL); return true; } bool BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) { + // ES6 specifies that loop variables get a fresh binding in each iteration. + // This is currently implemented for C-style for(;;) loops, but not + // for-in/of loops, though a similar approach should work. See bug 449811. + // + // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a + // scope containing an uninitialized `x`. If EXPR accesses `x`, we should + // get a ReferenceError due to the TDZ violation. This is not yet + // implemented. See bug 1069480. + *letDecl = pn->isKind(PNK_LEXICALSCOPE); MOZ_ASSERT_IF(*letDecl, pn->isLexical()); // If the left part is 'var x', emit code to define x if necessary using a // prologue opcode, but do not emit a pop. If it is 'let x', enterBlockScope // will initialize let bindings in emitForOf and emitForIn with // undefineds. // @@ -5313,17 +5321,16 @@ BytecodeEmitter::emitForInOrOfVariables( return false; } emittingForInit = false; } return true; } - bool BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top) { MOZ_ASSERT(type == StmtType::FOR_OF_LOOP || type == StmtType::SPREAD); MOZ_ASSERT_IF(type == StmtType::FOR_OF_LOOP, pn && pn->pn_left->isKind(PNK_FOROF)); MOZ_ASSERT_IF(type == StmtType::SPREAD, !pn); ParseNode* forHead = pn ? pn->pn_left : nullptr; @@ -5586,47 +5593,68 @@ BytecodeEmitter::emitForIn(ParseNode* pn if (letDecl) { if (!leaveNestedScope(&letStmt)) return false; } return true; } -bool -BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top) +/* C-style `for (init; cond; update) ...` loop. */ +bool +BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) { LoopStmtInfo stmtInfo(cx); pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top); ParseNode* forHead = pn->pn_left; ParseNode* forBody = pn->pn_right; - /* C-style for (init; cond; update) ... loop. */ + // If the head of this for-loop declared any lexical variables, the parser + // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the + // implicit scope of those variables. By the time we get here, we have + // already entered that scope. So far, so good. + // + // ### Scope freshening + // + // Each iteration of a `for (let V...)` loop creates a fresh loop variable + // binding for V, even if the loop is a C-style `for(;;)` loop: + // + // var funcs = []; + // for (let i = 0; i < 2; i++) + // funcs.push(function() { return i; }); + // assertEq(funcs[0](), 0); // the two closures capture... + // assertEq(funcs[1](), 1); // ...two different `i` bindings + // + // This is implemented by "freshening" the implicit block -- changing the + // scope chain to a fresh clone of the instantaneous block object -- each + // iteration, just before evaluating the "update" in for(;;) loops. + // + // No freshening occurs in `for (const ...;;)` as there's no point: you + // can't reassign consts. This is observable through the Debugger API. (The + // ES6 spec also skips cloning the environment in this case.) bool forLoopRequiresFreshening = false; if (ParseNode* init = forHead->pn_kid1) { - if (init->isKind(PNK_FRESHENBLOCK)) { - // The loop's init declaration was hoisted into an enclosing lexical - // scope node. Note that the block scope must be freshened each - // iteration. - forLoopRequiresFreshening = true; - } else { - emittingForInit = true; - if (!updateSourceCoordNotes(init->pn_pos.begin)) - return false; - if (!emitTree(init)) - return false; - emittingForInit = false; - - if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { - // 'init' is an expression, not a declaration. emitTree left - // its value on the stack. - if (!emit1(JSOP_POP)) - return false; - } + forLoopRequiresFreshening = init->isKind(PNK_LET); + + // Emit the `init` clause, whether it's an expression or a variable + // declaration. (The loop variables were hoisted into an enclosing + // scope, but we still need to emit code for the initializers.) + emittingForInit = true; + if (!updateSourceCoordNotes(init->pn_pos.begin)) + return false; + if (!emitTree(init)) + return false; + emittingForInit = false; + + if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emit1(JSOP_POP)) + return false; } } /* * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH). * Use tmp to hold the biased srcnote "top" offset, which differs * from the top local variable by the length of the JSOP_GOTO * emitted in between tmp and top if this loop has a condition. @@ -5744,17 +5772,17 @@ BytecodeEmitter::emitFor(ParseNode* pn, { if (pn->pn_left->isKind(PNK_FORIN)) return emitForIn(pn, top); if (pn->pn_left->isKind(PNK_FOROF)) return emitForOf(StmtType::FOR_OF_LOOP, pn, top); MOZ_ASSERT(pn->pn_left->isKind(PNK_FORHEAD)); - return emitNormalFor(pn, top); + return emitCStyleFor(pn, top); } MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) { FunctionBox* funbox = pn->pn_funbox; RootedFunction fun(cx, funbox->function()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
--- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -581,17 +581,17 @@ struct BytecodeEmitter bool emitSelfHostedCallFunction(ParseNode* pn); bool emitSelfHostedResumeGenerator(ParseNode* pn); bool emitSelfHostedForceInterpreter(ParseNode* pn); bool emitDo(ParseNode* pn); bool emitFor(ParseNode* pn, ptrdiff_t top); bool emitForIn(ParseNode* pn, ptrdiff_t top); bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl); - bool emitNormalFor(ParseNode* pn, ptrdiff_t top); + bool emitCStyleFor(ParseNode* pn, ptrdiff_t top); bool emitWhile(ParseNode* pn, ptrdiff_t top); bool emitBreak(PropertyName* label); bool emitContinue(PropertyName* label); bool emitDefaultsAndDestructuring(ParseNode* pn); bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp);
--- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -403,17 +403,16 @@ ContainsHoistedDeclaration(ExclusiveCont case PNK_GENEXP: case PNK_ARRAYCOMP: case PNK_ARGSBODY: case PNK_CATCHLIST: case PNK_CATCH: case PNK_FORIN: case PNK_FOROF: case PNK_FORHEAD: - case PNK_FRESHENBLOCK: case PNK_CLASSMETHOD: case PNK_CLASSMETHODLIST: case PNK_CLASSNAMES: case PNK_NEWTARGET: case PNK_POSHOLDER: case PNK_SUPERCALL: MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on " "some parent node without recurring to test this node"); @@ -1705,17 +1704,16 @@ Fold(ExclusiveContext* cx, ParseNode** p case PNK_DEBUGGER: case PNK_BREAK: case PNK_CONTINUE: case PNK_TEMPLATE_STRING: case PNK_THIS: case PNK_GENERATOR: case PNK_EXPORT_BATCH_SPEC: case PNK_OBJECT_PROPERTY_NAME: - case PNK_FRESHENBLOCK: case PNK_POSHOLDER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); return true; case PNK_TYPEOFNAME: MOZ_ASSERT(pn->isArity(PN_UNARY)); MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); MOZ_ASSERT(!pn->pn_kid->maybeExpr());
--- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -566,20 +566,16 @@ class FullParseHandler ParseNode* newForHead(ParseNodeKind kind, ParseNode* pn1, ParseNode* pn2, ParseNode* pn3, const TokenPos& pos) { MOZ_ASSERT(kind == PNK_FORIN || kind == PNK_FOROF || kind == PNK_FORHEAD); return new_<TernaryNode>(kind, JSOP_NOP, pn1, pn2, pn3, pos); } - ParseNode* newFreshenBlock(const TokenPos& pos) { - return new_<NullaryNode>(PNK_FRESHENBLOCK, pos); - } - ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) { TokenPos pos(begin, caseList->pn_pos.end); return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList); } ParseNode* newCaseOrDefault(uint32_t begin, ParseNode* expr, ParseNode* body) { TokenPos pos(begin, body->pn_pos.end); return new_<BinaryNode>(expr ? PNK_CASE : PNK_DEFAULT, JSOP_NOP, pos, expr, body);
--- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -369,17 +369,16 @@ class NameResolver case PNK_THIS: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: case PNK_BREAK: case PNK_CONTINUE: case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: - case PNK_FRESHENBLOCK: case PNK_OBJECT_PROPERTY_NAME: case PNK_POSHOLDER: MOZ_ASSERT(cur->isArity(PN_NULLARY)); break; case PNK_TYPEOFNAME: MOZ_ASSERT(cur->isArity(PN_UNARY)); MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME));
--- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -209,17 +209,16 @@ PushNodeChildren(ParseNode* pn, NodeStac case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: case PNK_BREAK: case PNK_CONTINUE: case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: case PNK_OBJECT_PROPERTY_NAME: - case PNK_FRESHENBLOCK: case PNK_POSHOLDER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately"); MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately"); return PushResult::Recyclable; // Nodes with a single non-null child. case PNK_TYPEOFNAME:
--- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -160,17 +160,16 @@ class PackedScopeCoordinate F(EXPORT_FROM) \ F(EXPORT_DEFAULT) \ F(EXPORT_SPEC_LIST) \ F(EXPORT_SPEC) \ F(EXPORT_BATCH_SPEC) \ F(FORIN) \ F(FOROF) \ F(FORHEAD) \ - F(FRESHENBLOCK) \ F(ARGSBODY) \ F(SPREAD) \ F(MUTATEPROTO) \ F(CLASS) \ F(CLASSMETHOD) \ F(CLASSMETHODLIST) \ F(CLASSNAMES) \ F(NEWTARGET) \
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4058,17 +4058,18 @@ Parser<FullParseHandler>::pushLetScope(H if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid))) return null(); return pn; } template <> SyntaxParseHandler::Node -Parser<SyntaxParseHandler>::pushLetScope(HandleStaticBlockObject blockObj, AutoPushStmtInfoPC& stmt) +Parser<SyntaxParseHandler>::pushLetScope(HandleStaticBlockObject blockObj, + AutoPushStmtInfoPC& stmt) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; } template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling) @@ -5182,29 +5183,47 @@ Parser<FullParseHandler>::forStatement(Y if (!report(ParseWarning, pc->sc->strict(), null(), JSMSG_DEPRECATED_FOR_EACH)) return null(); } } } MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); - /* - * True if we have 'for (var/let/const ...)'. - */ + // True if we have 'for (var/let/const ...)'. bool isForDecl = false; + // The next three variables are used to implement `for (let/const ...)`. + // + // We generate an implicit block, wrapping the whole loop, to store loop + // variables declared this way. Note that if the loop uses `for (var...)` + // instead, those variables go on some existing enclosing scope, so no + // implicit block scope is created. + // + // All three variables remain null/none if the loop is any other form. + // + // blockObj is the static block object for the implicit block scope. + RootedStaticBlockObject blockObj(context); + + // letStmt is the BLOCK StmtInfo for the implicit block. + // + // Caution: `letStmt.emplace()` creates some Rooted objects. Rooteds must + // be created/destroyed in FIFO order. Therefore adding a Rooted in this + // function, between this point and the .emplace() call below, would trip + // assertions. + Maybe<AutoPushStmtInfoPC> letStmt; + + // The PNK_LEXICALSCOPE node containing blockObj's ObjectBox. + ParseNode* forLetImpliedBlock = nullptr; + // True if a 'let' token at the head is parsed as an identifier instead of // as starting a declaration. bool letIsIdentifier = false; - /* Non-null when isForDecl is true for a 'for (let ...)' statement. */ - RootedStaticBlockObject blockObj(context); - - /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ + // Set to 'x' in 'for (x; ...; ...)' or 'for (x in ...)'. ParseNode* pn1; TokenStream::Modifier modifier = TokenStream::Operand; { TokenKind tt; if (!tokenStream.peekToken(&tt, TokenStream::Operand)) return null(); if (tt == TOK_SEMI) { @@ -5244,19 +5263,29 @@ Parser<FullParseHandler>::forStatement(Y bool constDecl = tt == TOK_CONST; isForDecl = true; blockObj = StaticBlockObject::create(context); if (!blockObj) return null(); // Initialize the enclosing scope manually for the call to // |variables| below. + blockObj = StaticBlockObject::create(context); + if (!blockObj) + return null(); blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope()); + letStmt.emplace(*this, StmtType::BLOCK); + forLetImpliedBlock = pushLetScope(blockObj, *letStmt); + if (!forLetImpliedBlock) + return null(); + (*letStmt)->isForLetBlock = true; + + MOZ_ASSERT(CurrentLexicalStaticBlock(pc) == blockObj); pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit, - nullptr, blockObj, DontHoistVars); + nullptr, blockObj, HoistVars); } else { pn1 = expr(InProhibited, yieldHandling, TripledotProhibited); } } else { // Pass |InProhibited| when parsing an expression so that |in| // isn't parsed in a RelationalExpression as a binary operator. // In this context, |in| is part of a for-in loop -- *not* part // of a binary expression. @@ -5264,70 +5293,20 @@ Parser<FullParseHandler>::forStatement(Y } if (!pn1) return null(); modifier = TokenStream::None; } } MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST)); - MOZ_ASSERT(!!blockObj == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST)))); - - // If the head of a for-loop declares any lexical variables, we generate an - // implicit block to store them. We implement this by desugaring. These: - // - // for (let/const <bindings>; <test>; <update>) <stmt> - // for (let <pattern> in <expr>) <stmt> - // for (let <pattern> of <expr>) <stmt> - // - // transform into roughly the same parse trees as these (using deprecated - // let-block syntax): - // - // let (<bindings>) { for (; <test>; <update>) <stmt> } - // let (<pattern>) { for (<pattern> in <expr>) <stmt> } - // let (<pattern>) { for (<pattern> of <expr>) <stmt> } - // - // This desugaring is not ES6 compliant. Initializers in the head of a - // let-block are evaluated *outside* the scope of the variables being - // initialized. ES6 mandates that they be evaluated in the same scope, - // triggering used-before-initialization temporal dead zone errors as - // necessary. See bug 1216623 on scoping and bug 1069480 on TDZ. - // - // Additionally, in ES6, each iteration of a for-loop creates a fresh - // binding of the loop variables. For example: - // - // var funcs = []; - // for (let i = 0; i < 2; i++) - // funcs.push(function() { return i; }); - // assertEq(funcs[0](), 0); // the two closures capture... - // assertEq(funcs[1](), 1); // ...two different `i` bindings - // - // These semantics are implemented by "freshening" the implicit block -- - // changing the scope chain to a fresh clone of the instantaneous block - // object -- each iteration, just before evaluating the "update" in - // for(;;) loops. We don't implement this freshening for for-in/of loops - // yet: bug 449811. - // - // No freshening occurs in `for (const ...;;)` as there's no point: you - // can't reassign consts. This is observable through the Debugger API. (The - // ES6 spec also skips cloning the environment in this case.) - // - // If the for-loop head includes a lexical declaration, then we create an - // implicit block scope, and: - // - // * forLetImpliedBlock is the node for the implicit block scope. - // * forLetDecl is the node for the decl 'let/const <pattern>'. - // - // Otherwise both are null. - ParseNode* forLetImpliedBlock = nullptr; - ParseNode* forLetDecl = nullptr; + MOZ_ASSERT(letStmt.isSome() == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST)))); // If there's an |in| keyword here, it's a for-in loop, by dint of careful // parsing of |pn1|. - Maybe<AutoPushStmtInfoPC> letStmt; /* used if blockObj != nullptr. */ ParseNode* pn2; /* forHead->pn_kid2 */ ParseNode* pn3; /* forHead->pn_kid3 */ ParseNodeKind headKind = PNK_FORHEAD; if (pn1) { bool isForIn, isForOf; if (!matchInOrOf(&isForIn, &isForOf)) return null(); @@ -5388,52 +5367,35 @@ Parser<FullParseHandler>::forStatement(Y // loop isn't valid ES6 and has never been permitted in // SpiderMonkey. report(ParseError, false, pn2, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT, headKind == PNK_FOROF ? "of" : "in"); return null(); } } else { /* Not a declaration. */ - MOZ_ASSERT(!blockObj); + MOZ_ASSERT(!letStmt); pn2 = pn1; pn1 = nullptr; if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment)) return null(); } pn3 = (headKind == PNK_FOROF) ? assignExpr(InAllowed, yieldHandling, TripledotProhibited) : expr(InAllowed, yieldHandling, TripledotProhibited); if (!pn3) return null(); modifier = TokenStream::None; - if (blockObj) { - /* - * Now that the pn3 has been parsed, push the let scope. To hold - * the blockObj for the emitter, wrap the PNK_LEXICALSCOPE node - * created by pushLetScope around the for's initializer. This also - * serves to indicate the let-decl to the emitter. - */ - letStmt.emplace(*this, StmtType::BLOCK); - ParseNode* block = pushLetScope(blockObj, *letStmt); - if (!block) - return null(); - (*letStmt)->isForLetBlock = true; - block->pn_expr = pn1; - block->pn_pos = pn1->pn_pos; - pn1 = block; - } - if (isForDecl) { /* * pn2 is part of a declaration. Make a copy that can be passed to - * EmitAssignment. Take care to do this after pushLetScope. + * BytecodeEmitter::emitAssignment. */ pn2 = cloneLeftHandSide(pn2); if (!pn2) return null(); } ParseNodeKind kind2 = pn2->getKind(); MOZ_ASSERT(kind2 != PNK_ASSIGN, "forStatement TOK_ASSIGN"); @@ -5445,57 +5407,23 @@ Parser<FullParseHandler>::forStatement(Y } else { if (isForEach) { reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP); return null(); } MOZ_ASSERT(headKind == PNK_FORHEAD); - if (blockObj) { + if (letStmt) { // Ensure here that the previously-unchecked assignment mandate for // const declarations holds. if (!checkForHeadConstInitializers(pn1)) { report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL); return null(); } - - // Desugar - // - // for (let INIT; TEST; UPDATE) STMT - // - // into - // - // let (INIT) { for (; TEST; UPDATE) STMT } - // - // to provide a block scope for INIT. - letStmt.emplace(*this, StmtType::BLOCK); - forLetImpliedBlock = pushLetScope(blockObj, *letStmt); - if (!forLetImpliedBlock) - return null(); - (*letStmt)->isForLetBlock = true; - - forLetDecl = pn1; - - // The above transformation isn't enough to implement |INIT| - // scoping, because each loop iteration must see separate bindings - // of |INIT|. We handle this by replacing the block on the scope - // chain with a new block, copying the old one's contents, each - // iteration. We supply a special PNK_FRESHENBLOCK node as the - // |let INIT| node for |for(let INIT;;)| loop heads to distinguish - // such nodes from *actual*, non-desugared use of the above syntax. - // (We don't do this for PNK_CONST nodes because the spec says no - // freshening happens -- observable with the Debugger API.) - if (pn1->isKind(PNK_CONST)) { - pn1 = nullptr; - } else { - pn1 = handler.newFreshenBlock(pn1->pn_pos); - if (!pn1) - return null(); - } } /* Parse the loop condition or null into pn2. */ MUST_MATCH_TOKEN_MOD(TOK_SEMI, modifier, JSMSG_SEMI_AFTER_FOR_INIT); TokenKind tt; if (!tokenStream.peekToken(&tt, TokenStream::Operand)) return null(); if (tt == TOK_SEMI) { @@ -5537,17 +5465,17 @@ Parser<FullParseHandler>::forStatement(Y ParseNode* forLoop = handler.newForStatement(begin, forHead, body, iflags); if (!forLoop) return null(); if (forLetImpliedBlock) { forLetImpliedBlock->pn_expr = forLoop; forLetImpliedBlock->pn_pos = forLoop->pn_pos; - return handler.newLetBlock(forLetDecl, forLetImpliedBlock, forLoop->pn_pos); + return forLetImpliedBlock; } return forLoop; } template <> SyntaxParseHandler::Node Parser<SyntaxParseHandler>::forStatement(YieldHandling yieldHandling) { @@ -8288,16 +8216,17 @@ Parser<ParseHandler>::comprehensionFor(G TokenPos headPos(begin, pos().end); AutoPushStmtInfoPC stmtInfo(*this, StmtType::BLOCK); BindData<ParseHandler> data(context); RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); if (!blockObj) return null(); + // Initialize the enclosing scope manually for the call to |bind| // below, which is before the call to |pushLetScope|. blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope()); data.initLexical(DontHoistVars, JSOP_DEFLET, blockObj, JSMSG_TOO_MANY_LOCALS); Node decls = handler.newList(PNK_LET, lhs); if (!decls) return null();
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/bug1219954.js @@ -0,0 +1,12 @@ +"use strict"; + +if (!('oomTest' in this)) + quit(); + +let g = (function() { + "use asm"; + function f() {} + return f; +})(); + +oomTest(() => "" + g);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1219363.js @@ -0,0 +1,9 @@ +var x = [1, 2, , 4] +x[100000] = 1; +var y = Object.create(x); +y.a = 1; +y.b = 1; +var arr = []; +for (var z in y) + arr.push(z); +assertEq(arr.join(), "a,b,0,1,3,100000");
--- a/js/src/jit-test/tests/basic/bug646968-3.js +++ b/js/src/jit-test/tests/basic/bug646968-3.js @@ -1,16 +1,16 @@ -var s, x = 0; +var s, v = "NOPE"; s = ''; -for (let x = x; x < 3; x++) +for (let v = 0, x = v; x < 3; x++) s += x; assertEq(s, '012'); s = ''; -for (let x = eval('x'); x < 3; x++) +for (let v = 0, x = eval('v'); x < 3; x++) s += x; assertEq(s, '012'); s = '' -for (let x = function () { with ({}) return x; }(); x < 3; x++) +for (let v = 0, x = function () { with ({}) return v; }(); x < 3; x++) s += x; assertEq(s, '012');
--- a/js/src/jit-test/tests/basic/bug646968-4.js +++ b/js/src/jit-test/tests/basic/bug646968-4.js @@ -1,4 +1,8 @@ -var s = '', x = {a: 1, b: 2, c: 3}; +// Scoping: `x` in the head of a `for (let x...)` loop refers to the loop variable. + +// For now, this means it evaluates to undefined. It ought to throw a +// ReferenceError instead, but the TDZ isn't implemented here (bug 1069480). +var s = "", x = {a: 1, b: 2, c: 3}; for (let x in eval('x')) s += x; -assertEq(s, 'abc'); +assertEq(s, "");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug646968-6.js @@ -0,0 +1,16 @@ +// In `for (let x = EXPR; ;)`, if `x` appears within EXPR, it refers to the +// loop variable. Actually doing this is typically a TDZ error. + +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(() => { + for (let x = x; null.foo; null.foo++) {} +}, ReferenceError); + +assertThrowsInstanceOf(() => { + for (let x = eval('x'); null.foo; null.foo++) {} +}, ReferenceError); + +assertThrowsInstanceOf(() => { + for (let x = function () { with ({}) return x; }(); null.foo; null.foo++) {} +}, ReferenceError);
deleted file mode 100644 --- a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js +++ /dev/null @@ -1,5 +0,0 @@ -var x = "foobar"; -{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); } - -var x = "foobar"; -{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); }
--- a/js/src/jit-test/tests/basic/testLet.js +++ b/js/src/jit-test/tests/basic/testLet.js @@ -1,14 +1,15 @@ var otherGlobal = newGlobal(); function test(str, arg, result) { arg = arg || 'ponies'; - result = result || 'ponies'; + if (arguments.length < 3) + result = 'ponies'; var fun = new Function('x', str); var got = fun.toSource(); var expect = '(function anonymous(x) {\n' + str + '\n})'; if (got !== expect) { print("GOT: " + got); print("EXPECT: " + expect); @@ -131,33 +132,27 @@ test('this.y = x;if (x) {let y = 1;retur // for(;;) test('for (;;) {return x;}'); test('for (let y = 1;;) {return x;}'); test('for (let y = 1;; ++y) {return x;}'); test('for (let y = 1; ++y;) {return x;}'); test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}'); test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6); -test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6); -test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1); -test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6); -test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); -test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); +test('var sum = 0;for (let x = 1; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); test('for (var y = 1;;) {return x;}'); test('for (var y = 1;; ++y) {return x;}'); test('for (var y = 1; ++y;) {return x;}'); test('for (var X = 1, [y, z] = x, a = x; z < 4; ++z) {return X + y;}', [2,3], 3); test('var sum = 0;for (var y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6); test('var sum = 0;for (var X = x, y = 10; X < 4; ++X) {sum += X;}return sum;', 1, 6); test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1); test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6); test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); -test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}'); -test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie'); test('for (let y = x;;) {let x;return y;}'); test('for (let y = x;;) {let y;return x;}'); test('for (let y;;) {let y;return x;}'); test('for (let a = x;;) {let c = x, d = x;return c;}'); test('for (let [a, b] = x;;) {let c = x, d = x;return c;}'); test('for (let [a] = (1, [x]);;) {return a;}'); test('for (let [a] = (1, x, 1, x);;) {return a;}', ['ponies']); isParseError('for (let x = 1, x = 2;;) {}'); @@ -165,38 +160,41 @@ isParseError('for (let [x, y] = a, {a:x} isParseError('for (let [x, y, x] = a;;) {}'); isParseError('for (let [x, [y, [x]]] = a;;) {}'); // for(in) test('for (let i in x) {return x;}'); test('for (let i in x) {let y;return x;}'); test('for each (let [a, b] in x) {let y;return x;}'); test('for (let i in x) {let i = x;return i;}'); -test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]); -test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]); test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011'); test('var res = "";for (let i in x) {res += x[i];}return res;'); test('var res = "";for (var i in x) {res += x[i];}return res;'); -test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}'); -test('for (let x in eval("x")) {return x;}', {ponies:true}); -test('for (let x in x) {return eval("x");}', {ponies:true}); -test('for (let x in eval("x")) {return eval("x");}', {ponies:true}); +isParseError('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}'); test('for (let i in x) {break;}return x;'); test('for (let i in x) {break;}return eval("x");'); -test('for (let x in x) {break;}return x;'); -test('for (let x in x) {break;}return eval("x");'); test('a:for (let i in x) {for (let j in x) {break a;}}return x;'); test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");'); test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true}); -test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}'); -test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']); isParseError('for (let [x, x] in o) {}'); isParseError('for (let [x, y, x] in o) {}'); isParseError('for (let [x, [y, [x]]] in o) {}'); +// for(let ... in ...) scoping bugs (bug 1069480) +test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']], undefined); +test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']], undefined); +test('for (let x in eval("x")) {return x;}', {ponies:true}, undefined); +test('for (let x in x) {return eval("x");}', {ponies:true}, undefined); +test('for (let x in eval("x")) {return eval("x");}', {ponies:true}, undefined); +test('for (let x in x) {break;}return x;'); +test('for (let x in x) {break;}return eval("x");'); +test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}', undefined, undefined); +test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies'], undefined); +test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}', undefined, undefined); + // genexps test('return (i for (i in x)).next();', {ponies:true}); test('return (eval("i") for (i in x)).next();', {ponies:true}); test('return (eval("i") for (i in eval("x"))).next();', {ponies:true}); test('try {return (eval("throw i") for (i in x)).next();} catch (e) {return e;}', {ponies:true}); // array comprehension test('return [i for (i in x)][0];', {ponies:true}); @@ -217,11 +215,18 @@ isReferenceError('inner(); let x; functi isReferenceError('inner(); let x; function inner() { function innerer() { x++; } innerer(); }'); isReferenceError('let x; var inner = function () { y++; }; inner(); let y;'); isReferenceError('let x = x;'); isReferenceError('let [x] = [x];'); isReferenceError('let {x} = {x:x};'); isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}'); isReferenceError('let x = function() {} ? x() : function() {}'); isReferenceError('(function() { let x = (function() { return x }()); }())'); +isReferenceError('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;'); +isReferenceError('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;'); +isReferenceError('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;'); +isReferenceError('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;'); +isReferenceError('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;'); +isReferenceError('for (let x = eval("throw x");;) {}'); +isReferenceError('for (let x = x + "s"; eval("throw x");) {}'); // redecl with function statements isParseError('let a; function a() {}');
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/modules/bug-1219408.js @@ -0,0 +1,2 @@ +// |jit-test| error: Error +parseModule("").evaluation();
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -9295,59 +9295,16 @@ CodeGenerator::visitAtomicIsLockFree(LAt masm.branch32(Assembler::Equal, value, Imm32(1), &Ldone); if (!AtomicOperations::isLockfree8()) masm.bind(&Lfailed); masm.move32(Imm32(0), output); masm.bind(&Ldone); } void -CodeGenerator::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) -{ - Register elements = ToRegister(lir->elements()); - AnyRegister output = ToAnyRegister(lir->output()); - Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); - - Register oldval = ToRegister(lir->oldval()); - Register newval = ToRegister(lir->newval()); - - Scalar::Type arrayType = lir->mir()->arrayType(); - int width = Scalar::byteSize(arrayType); - - if (lir->index()->isConstant()) { - Address dest(elements, ToInt32(lir->index()) * width); - masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); - } else { - BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); - masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); - } -} - -void -CodeGenerator::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) -{ - Register elements = ToRegister(lir->elements()); - AnyRegister output = ToAnyRegister(lir->output()); - Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); - - Register value = ToRegister(lir->value()); - - Scalar::Type arrayType = lir->mir()->arrayType(); - int width = Scalar::byteSize(arrayType); - - if (lir->index()->isConstant()) { - Address dest(elements, ToInt32(lir->index()) * width); - masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); - } else { - BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); - masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); - } -} - -void CodeGenerator::visitClampIToUint8(LClampIToUint8* lir) { Register output = ToRegister(lir->output()); MOZ_ASSERT(output == ToRegister(lir->input())); masm.clampIntToUint8(output); } void
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -281,18 +281,16 @@ class CodeGenerator : public CodeGenerat void visitArrayConcat(LArrayConcat* lir); void visitArraySlice(LArraySlice* lir); void visitArrayJoin(LArrayJoin* lir); void visitLoadUnboxedScalar(LLoadUnboxedScalar* lir); void visitLoadTypedArrayElementHole(LLoadTypedArrayElementHole* lir); void visitStoreUnboxedScalar(LStoreUnboxedScalar* lir); void visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir); void visitAtomicIsLockFree(LAtomicIsLockFree* lir); - void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); - void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitClampIToUint8(LClampIToUint8* lir); void visitClampDToUint8(LClampDToUint8* lir); void visitClampVToUint8(LClampVToUint8* lir); void visitCallIteratorStart(LCallIteratorStart* lir); void visitIteratorStart(LIteratorStart* lir); void visitIteratorMore(LIteratorMore* lir); void visitIsNoIterAndBranch(LIsNoIterAndBranch* lir); void visitIteratorEnd(LIteratorEnd* lir);
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -11864,32 +11864,28 @@ IonBuilder::tryInnerizeWindow(MDefinitio } bool IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* name, TemporaryTypeSet* types) { // See the comment in tryInnerizeWindow for how this works. + // Note that it's important that we do this _before_ we'd try to + // do the optimizations below on obj normally, since some of those + // optimizations have fallback paths that are slower than the path + // we'd produce here. + MOZ_ASSERT(*emitted == false); MDefinition* inner = tryInnerizeWindow(obj); if (inner == obj) return true; if (!forceInlineCaches()) { - // Note: the Baseline ICs don't know about this optimization, so it's - // possible the global property's HeapTypeSet has not been initialized - // yet. In this case we'll fall back to getPropTryCache for now. - - // Note that it's important that we do this _before_ we'd try to - // do the optimizations below on obj normally, since some of those - // optimizations have fallback paths that are slower than the path - // we'd produce here. - trackOptimizationAttempt(TrackedStrategy::GetProp_Constant); if (!getPropTryConstant(emitted, inner, NameToId(name), types) || *emitted) return *emitted; trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName); if (!getStaticName(&script()->global(), name, emitted) || *emitted) return *emitted;
--- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -441,105 +441,16 @@ MacroAssembler::loadFromTypedArray(Scala } } template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const Address& src, const ValueOperand& dest, bool allowDouble, Register temp, Label* fail); template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest, bool allowDouble, Register temp, Label* fail); -template<typename T> -void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, - Register oldval, Register newval, - Register temp, AnyRegister output) -{ - switch (arrayType) { - case Scalar::Int8: - compareExchange8SignExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint8: - compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint8Clamped: - compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Int16: - compareExchange16SignExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint16: - compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Int32: - compareExchange32(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint32: - // At the moment, the code in MCallOptimize.cpp requires the output - // type to be double for uint32 arrays. See bug 1077305. - MOZ_ASSERT(output.isFloat()); - compareExchange32(mem, oldval, newval, temp); - convertUInt32ToDouble(temp, output.fpu()); - break; - default: - MOZ_CRASH("Invalid typed array type"); - } -} - -template void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, - Register oldval, Register newval, Register temp, - AnyRegister output); -template void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, - Register oldval, Register newval, Register temp, - AnyRegister output); - -template<typename T> -void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, - Register value, Register temp, AnyRegister output) -{ - switch (arrayType) { - case Scalar::Int8: - atomicExchange8SignExtend(mem, value, output.gpr()); - break; - case Scalar::Uint8: - atomicExchange8ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Uint8Clamped: - atomicExchange8ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Int16: - atomicExchange16SignExtend(mem, value, output.gpr()); - break; - case Scalar::Uint16: - atomicExchange16ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Int32: - atomicExchange32(mem, value, output.gpr()); - break; - case Scalar::Uint32: - // At the moment, the code in MCallOptimize.cpp requires the output - // type to be double for uint32 arrays. See bug 1077305. - MOZ_ASSERT(output.isFloat()); - atomicExchange32(mem, value, temp); - convertUInt32ToDouble(temp, output.fpu()); - break; - default: - MOZ_CRASH("Invalid typed array type"); - } -} - -template void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, - Register value, Register temp, AnyRegister output); -template void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, - Register value, Register temp, AnyRegister output); - template <typename T> void MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output) { switch (type) { case JSVAL_TYPE_INT32: { // Handle loading an int32 into a double reg. if (output.type() == MIRType_Double) {
--- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1069,24 +1069,16 @@ class MacroAssembler : public MacroAssem case Scalar::Uint32: store32(value, dest); break; default: MOZ_CRASH("Invalid typed array type"); } } - template<typename T> - void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, - Register temp, AnyRegister output); - - template<typename T> - void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, - Register temp, AnyRegister output); - void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest, unsigned numElems = 0); void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest, unsigned numElems = 0); // Load a property from an UnboxedPlainObject or UnboxedArrayObject. template <typename T> void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output);
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -532,17 +532,17 @@ NewStringObject(JSContext* cx, HandleStr { return StringObject::create(cx, str); } bool OperatorIn(JSContext* cx, HandleValue key, HandleObject obj, bool* out) { RootedId id(cx); - return ValueToId<CanGC>(cx, key, &id) && + return ToPropertyKey(cx, key, &id) && HasProperty(cx, obj, id, out); } bool OperatorInI(JSContext* cx, uint32_t index, HandleObject obj, bool* out) { RootedValue key(cx, Int32Value(index)); return OperatorIn(cx, key, obj, out);
--- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -1685,16 +1685,58 @@ CodeGeneratorARM::visitLoadTypedArrayEle } void CodeGeneratorARM::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins) { MOZ_CRASH("NYI"); } +void +CodeGeneratorARM::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorARM::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} template<typename S, typename T> void CodeGeneratorARM::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value, const T& mem, Register flagTemp, Register outTemp, AnyRegister output) { MOZ_ASSERT(flagTemp != InvalidReg);
--- a/js/src/jit/arm/CodeGenerator-arm.h +++ b/js/src/jit/arm/CodeGenerator-arm.h @@ -191,16 +191,18 @@ class CodeGeneratorARM : public CodeGene void visitNegI(LNegI* lir); void visitNegD(LNegD* lir); void visitNegF(LNegF* lir); void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins); void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins); void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir); + void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitAsmJSCall(LAsmJSCall* ins); void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins); void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins); void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins); void visitAsmJSCompareExchangeCallout(LAsmJSCompareExchangeCallout* ins); void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins); void visitAsmJSAtomicExchangeCallout(LAsmJSAtomicExchangeCallout* ins); void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
--- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -4822,16 +4822,105 @@ js::jit::MacroAssemblerARMCompat::atomic const BaseIndex& mem, Register flagTemp); template void js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value, const Address& mem, Register flagTemp); template void js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value, const BaseIndex& mem, Register flagTemp); +template<typename T> +void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template<typename T> +void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + void MacroAssemblerARMCompat::profilerEnterFrame(Register framePtr, Register scratch) { AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); loadPtr(activation, scratch); storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); }
--- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1611,16 +1611,24 @@ class MacroAssemblerARMCompat : public M void atomicXor16(const S& value, const T& mem, Register flagTemp) { atomicEffectOp(2, AtomicFetchXorOp, value, mem, flagTemp); } template <typename T, typename S> void atomicXor32(const S& value, const T& mem, Register flagTemp) { atomicEffectOp(4, AtomicFetchXorOp, value, mem, flagTemp); } + template<typename T> + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template<typename T> + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + void clampIntToUint8(Register reg) { // Look at (reg >> 8) if it is 0, then reg shouldn't be clamped if it is // <0, then we want to clamp to 0, otherwise, we wish to clamp to 255 ScratchRegisterScope scratch(asMasm()); as_mov(scratch, asr(reg, 8), SetCC); ma_mov(Imm32(0xff), reg, LeaveCC, NotEqual); ma_mov(Imm32(0), reg, LeaveCC, Signed); }
--- a/js/src/jit/arm64/CodeGenerator-arm64.cpp +++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp @@ -732,8 +732,52 @@ CodeGeneratorARM64::setReturnDoubleRegs( { MOZ_ASSERT(ReturnFloat32Reg.code_ == FloatRegisters::s0); MOZ_ASSERT(ReturnDoubleReg.code_ == FloatRegisters::d0); FloatRegister s1 = {FloatRegisters::s1, FloatRegisters::Single}; regs->add(ReturnFloat32Reg); regs->add(s1); regs->add(ReturnDoubleReg); } + +void +CodeGeneratorARM64::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorARM64::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} +
--- a/js/src/jit/arm64/CodeGenerator-arm64.h +++ b/js/src/jit/arm64/CodeGenerator-arm64.h @@ -202,16 +202,18 @@ class CodeGeneratorARM64 : public CodeGe void visitInterruptCheck(LInterruptCheck* lir); void visitNegI(LNegI* lir); void visitNegD(LNegD* lir); void visitNegF(LNegF* lir); void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins); void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins); + void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitAsmJSCall(LAsmJSCall* ins); void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins); void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins); void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins); void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins); void visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar* ins); void visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar* ins); void visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr* ins);
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp +++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp @@ -255,16 +255,105 @@ MacroAssemblerCompat::branchValueIsNurse void MacroAssemblerCompat::breakpoint() { static int code = 0xA77; Brk((code++) & 0xffff); } +template<typename T> +void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template<typename T> +void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. void MacroAssembler::PushRegsInMask(LiveRegisterSet set) { for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); ) {
--- a/js/src/jit/arm64/MacroAssembler-arm64.h +++ b/js/src/jit/arm64/MacroAssembler-arm64.h @@ -2858,16 +2858,24 @@ class MacroAssemblerCompat : public vixl void atomicXor16(const S& value, const T& mem) { atomicEffectOp(2, AtomicFetchXorOp, value, mem); } template <typename T, typename S> void atomicXor32(const S& value, const T& mem) { atomicEffectOp(4, AtomicFetchXorOp, value, mem); } + template<typename T> + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template<typename T> + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + // Emit a BLR or NOP instruction. ToggleCall can be used to patch // this instruction. CodeOffsetLabel toggledCall(JitCode* target, bool enabled) { // The returned offset must be to the first instruction generated, // for the debugger to match offset with Baseline's pcMappingEntries_. BufferOffset offset = nextOffset(); syncStackPtr();
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -3315,16 +3315,59 @@ CodeGeneratorX86Shared::visitSimdSelect( masm.packedRightShiftByScalar(Imm32(31), temp); } masm.bitwiseAndX4(Operand(temp), output); masm.bitwiseAndNotX4(Operand(onFalse), temp); masm.bitwiseOrX4(Operand(temp), output); } +void +CodeGeneratorX86Shared::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorX86Shared::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} + template<typename S, typename T> void CodeGeneratorX86Shared::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value, const T& mem, Register temp1, Register temp2, AnyRegister output) { // Uint8Clamped is explicitly not supported here switch (arrayType) { case Scalar::Int8:
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h @@ -238,16 +238,18 @@ class CodeGeneratorX86Shared : public Co virtual void visitGuardClass(LGuardClass* guard); virtual void visitEffectiveAddress(LEffectiveAddress* ins); virtual void visitUDivOrMod(LUDivOrMod* ins); virtual void visitUDivOrModConstant(LUDivOrModConstant *ins); virtual void visitAsmJSPassStackArg(LAsmJSPassStackArg* ins); virtual void visitMemoryBarrier(LMemoryBarrier* ins); virtual void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); virtual void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir); + virtual void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + virtual void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool); void visitOffsetBoundsCheck(OffsetBoundsCheck* oolCheck); void visitNegI(LNegI* lir); void visitNegD(LNegD* lir); void visitNegF(LNegF* lir);
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp @@ -138,16 +138,106 @@ MacroAssemblerX86Shared::asMasm() } const MacroAssembler& MacroAssemblerX86Shared::asMasm() const { return *static_cast<const MacroAssembler*>(this); } +template<typename T> +void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + asMasm().convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template<typename T> +void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + asMasm().convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + + //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. void MacroAssembler::PushRegsInMask(LiveRegisterSet set) { FloatRegisterSet fpuSet(set.fpus().reduceSetForPush());
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h @@ -1450,16 +1450,24 @@ class MacroAssemblerX86Shared : public A CodeOffsetLabel labelForPatch() { return CodeOffsetLabel(size()); } void abiret() { ret(); } + template<typename T> + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template<typename T> + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + protected: bool buildOOLFakeExitFrame(void* fakeReturnAddr); }; template <> inline void MacroAssemblerX86Shared::loadAlignedVector<int32_t>(const Address& src, FloatRegister dest) { loadAlignedInt32x4(src, dest); }
--- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -176,16 +176,17 @@ static bool EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr) { bool enumerateSymbols; if (flags & JSITER_SYMBOLSONLY) { enumerateSymbols = true; } else { /* Collect any dense elements from this object. */ + size_t firstElemIndex = props->length(); size_t initlen = pobj->getDenseInitializedLength(); const Value* vp = pobj->getDenseElements(); bool hasHoles = false; for (size_t i = 0; i < initlen; ++i, ++vp) { if (vp->isMagic(JS_ELEMENTS_HOLE)) { hasHoles = true; } else { /* Dense arrays never get so large that i would not fit into an integer id. */ @@ -201,34 +202,35 @@ EnumerateNativeProperties(JSContext* cx, if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) return false; } } // Collect any sparse elements from this object. bool isIndexed = pobj->isIndexed(); if (isIndexed) { - size_t numElements = props->length(); + // If the dense elements didn't have holes, we don't need to include + // them in the sort. + if (!hasHoles) + firstElemIndex = props->length(); for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) { Shape& shape = r.front(); jsid id = shape.propid(); uint32_t dummy; if (IdIsIndex(id, &dummy)) { if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props)) return false; } } - // If the dense elements didn't have holes, we don't need to include - // them in the sort. - size_t startIndex = hasHoles ? 0 : numElements; + MOZ_ASSERT(firstElemIndex <= props->length()); - jsid* ids = props->begin() + startIndex; - size_t n = props->length() - startIndex; + jsid* ids = props->begin() + firstElemIndex; + size_t n = props->length() - firstElemIndex; AutoIdVector tmp(cx); if (!tmp.resize(n)) return false; PodCopy(tmp.begin(), ids, n); if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds)) return false;
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js @@ -0,0 +1,94 @@ +var symbols = [ + Symbol(), Symbol("iterator"), Symbol.for("iterator"), Symbol.iterator +]; + +for (var sym of symbols) { + var key = { + toString() { return sym; } + }; + + // Test that ToPropertyKey can return a symbol in each of the following + // contexts. + + // Computed property names. + var obj = {[key]: 13}; + var found = Reflect.ownKeys(obj); + assertEq(found.length, 1); + assertEq(found[0], sym); + + // Computed accessor property names. + var obj2 = { + get [key]() { return "got"; }, + set [key](v) { this.v = v; } + }; + assertEq(obj2[sym], "got"); + obj2[sym] = 33; + assertEq(obj2.v, 33); + + // Getting and setting properties. + assertEq(obj[key], 13); + obj[key] = 19; + assertEq(obj[sym], 19); + (function () { "use strict"; obj[key] = 20; })(); + assertEq(obj[sym], 20); + obj[key]++; + assertEq(obj[sym], 21); + + // Getting properties of primitive values. + Number.prototype[sym] = "success"; + assertEq(Math.PI[key], "success"); + delete Number.prototype[sym]; + + if (classesEnabled()) { + eval(` + // Getting a super property. + class X { + [sym]() { return "X"; } + } + class Y extends X { + [sym]() { return super[key]() + "Y"; } + } + var y = new Y(); + assertEq(y[sym](), "XY"); + + // Setting a super property. + class Z { + set [sym](v) { + this.self = this; + this.value = v; + } + } + class W extends Z { + set [sym](v) { + this.isW = true; + super[key] = v; + } + } + var w = new W(); + w[key] = "ok"; + assertEq(w.self, w); + assertEq(w.value, "ok"); + assertEq(w.isW, true); + `); + } + + // Deleting properties. + obj = {[sym]: 1}; + assertEq(delete obj[key], true); + assertEq(sym in obj, false); + + // LHS of `in` expressions. + assertEq(key in {iterator: 0}, false); + assertEq(key in {[sym]: 0}, true); + + // Methods of Object and Object.prototype + obj = {}; + Object.defineProperty(obj, key, {value: "ok", enumerable: true}); + assertEq(obj[sym], "ok"); + assertEq(obj.hasOwnProperty(key), true); + assertEq(obj.propertyIsEnumerable(key), true); + var desc = Object.getOwnPropertyDescriptor(obj, key); + assertEq(desc.value, "ok"); +} + +reportCompare(0, 0);
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js @@ -0,0 +1,19 @@ +// Scoping in the head of for(let;;) statements. + +let x = 0; +for (let i = 0, a = () => i; i < 4; i++) { + assertEq(i, x++); + assertEq(a(), 0); +} +assertEq(x, 4); + +x = 11; +let q = 0; +for (let {[++q]: r} = [0, 11, 22], s = () => r; r < 13; r++) { + assertEq(r, x++); + assertEq(s(), 11); +} +assertEq(x, 13); +assertEq(q, 1); + +reportCompare(0, 0);
--- a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js +++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js @@ -73,21 +73,17 @@ assertEq(funcs[6](), 6); assertEq(funcs[7](), 7); assertEq(funcs[8](), 8); assertEq(funcs[9](), 9); var outer = "OUTER V IGNORE"; var save; for (let outer = (save = function() { return outer; }); ; ) break; -assertEq(save(), "OUTER V IGNORE", - "this is actually a bug: fix for(;;) loops to evaluate init RHSes " + - "in the block scope containing all the LHS bindings!"); - - +assertEq(save(), save); var funcs = []; function t(i, name, expect) { assertEq(funcs[i].name, name); assertEq(funcs[i](), expect); }
--- a/js/src/tests/ecma_6/Reflect/propertyKeys.js +++ b/js/src/tests/ecma_6/Reflect/propertyKeys.js @@ -39,16 +39,22 @@ var keys = [ }, expected: "fallback" }, { value: { [Symbol.toPrimitive](hint) { return hint; } }, expected: "string" + }, + { + value: { + [Symbol.toPrimitive](hint) { return Symbol.for(hint); } + }, + expected: Symbol.for("string") } ]; for (var {value, expected} of keys) { if (expected === undefined) expected = value; var obj = {};
--- a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js +++ b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js @@ -18,19 +18,19 @@ function testVarPatternCombinations(make assertBlockDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i])); assertDecl("const " + constSrcs[i].join(",") + ";", constDecl(constPatts[i])); // variable declarations in for-loop heads assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);", forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);", - letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); + forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (const " + constSrcs[i].join(",") + "; foo; bar);", - letStmt(constPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); + forStmt(constDecl(constPatts[i]), ident("foo"), ident("bar"), emptyStmt)); } } testVarPatternCombinations(n => ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "} = 0"), n => ({ id: objPatt([assignProp("a" + n, ident("x" + n)), assignProp("b" + n, ident("y" + n)), assignProp("c" + n, ident("z" + n))]), init: lit(0) }));
--- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/DateTime.h" +#include "mozilla/Atomics.h" + #include <time.h> #include "jsutil.h" #include "js/Date.h" #if ENABLE_INTL_API #include "unicode/timezone.h" #endif @@ -18,16 +20,19 @@ using mozilla::UnspecifiedNaN; /* static */ js::DateTimeInfo js::DateTimeInfo::instance; /* static */ mozilla::Atomic<bool, mozilla::ReleaseAcquire> js::DateTimeInfo::AcquireLock::spinLock; +/* extern */ mozilla::Atomic<js::IcuTimeZoneStatus, mozilla::ReleaseAcquire> +js::DefaultTimeZoneStatus; + static bool ComputeLocalTime(time_t local, struct tm* ptm) { #if defined(_WIN32) return localtime_s(ptm, &local) == 0; #elif defined(HAVE_LOCALTIME_R) return localtime_r(&local, ptm); #else @@ -307,12 +312,16 @@ js::DateTimeInfo::sanityCheck() rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT); } JS_PUBLIC_API(void) JS::ResetTimeZone() { js::DateTimeInfo::updateTimeZoneAdjustment(); -#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) - icu::TimeZone::recreateDefault(); -#endif + // Trigger lazy recreation of ICU's default time zone, if needed and not + // already being performed. (If it's already being performed, behavior + // will be safe but racy.) See also Intl.cpp:NewUDateFormat which performs + // the recreation. Note that if new places observing ICU's default time + // zone are added, they'll need to do the same things NewUDateFormat does. + js::DefaultTimeZoneStatus.compareExchange(js::IcuTimeZoneStatus::Valid, + js::IcuTimeZoneStatus::NeedsUpdate); }
--- a/js/src/vm/DateTime.h +++ b/js/src/vm/DateTime.h @@ -195,11 +195,37 @@ class DateTimeInfo static const int64_t RangeExpansionAmount = 30 * SecondsPerDay; int64_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds); void internalUpdateTimeZoneAdjustment(); void sanityCheck(); }; +enum class IcuTimeZoneStatus : uint32_t +{ + // ICU's current default time zone is accurate. + Valid = 0, + + // ICU's current default time zone may not be consistent with + // DateTimeInfo::localTZA(). + NeedsUpdate, + + // We're in the middle of recreating ICU's default time zone. + Updating +}; + +// The user's current time zone adjustment and time zone are computed in two +// places: via DateTimeInfo::localTZA(), and via ICU. The mechanisms are, +// unfortunately, separate: both must be triggered to respond to a time zone +// change. +// +// Updating ICU's default time zone is a relatively slow operation. If we +// perform it exactly when we update DateTimeInfo::localTZA(), we regress perf +// per bug 1220693. Instead, we defer updating ICU until we actually need the +// data. We record needs-update status in this global (necessarily atomic) +// variable. +extern mozilla::Atomic<IcuTimeZoneStatus, mozilla::ReleaseAcquire> +DefaultTimeZoneStatus; + } /* namespace js */ #endif /* vm_DateTime_h */
--- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -578,17 +578,17 @@ ToIdOperation(JSContext* cx, HandleScrip return true; } JSObject* obj = ToObjectFromStack(cx, objval); if (!obj) return false; RootedId id(cx); - if (!ValueToId<CanGC>(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; res.set(IdToValue(id)); return true; } static MOZ_ALWAYS_INLINE bool GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::HandleObject receiver, @@ -603,44 +603,35 @@ GetObjectElementOperation(JSContext* cx, if (GetElementNoGC(cx, obj, receiver, index, res.address())) break; if (!GetElement(cx, obj, receiver, index, res)) return false; break; } - if (IsSymbolOrSymbolWrapper(key)) { - RootedId id(cx, SYMBOL_TO_JSID(ToSymbolPrimitive(key))); - if (!GetProperty(cx, obj, receiver, id, res)) + if (key.isString()) { + JSString* str = key.toString(); + JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str); + if (!name) return false; - break; - } - - if (JSAtom* name = ToAtom<NoGC>(cx, key)) { if (name->isIndex(&index)) { if (GetElementNoGC(cx, obj, receiver, index, res.address())) break; } else { if (GetPropertyNoGC(cx, obj, receiver, name->asPropertyName(), res.address())) break; } } - JSAtom* name = ToAtom<CanGC>(cx, key); - if (!name) + RootedId id(cx); + if (!ToPropertyKey(cx, key, &id)) return false; - - if (name->isIndex(&index)) { - if (!GetElement(cx, obj, receiver, index, res)) - return false; - } else { - if (!GetProperty(cx, obj, receiver, name->asPropertyName(), res)) - return false; - } + if (!GetProperty(cx, obj, receiver, id, res)) + return false; } while (false); #if JS_HAS_NO_SUCH_METHOD if (op == JSOP_CALLELEM && MOZ_UNLIKELY(res.isUndefined())) { if (!OnUnknownMethod(cx, receiver, key, res)) return false; } #endif @@ -668,44 +659,35 @@ GetPrimitiveElementOperation(JSContext* if (GetElementNoGC(cx, boxed, receiver, index, res.address())) break; if (!GetElement(cx, boxed, receiver, index, res)) return false; break; } - if (IsSymbolOrSymbolWrapper(key)) { - RootedId id(cx, SYMBOL_TO_JSID(ToSymbolPrimitive(key))); - if (!GetProperty(cx, boxed, receiver, id, res)) + if (key.isString()) { + JSString* str = key.toString(); + JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str); + if (!name) return false; - break; - } - - if (JSAtom* name = ToAtom<NoGC>(cx, key)) { if (name->isIndex(&index)) { if (GetElementNoGC(cx, boxed, receiver, index, res.address())) break; } else { if (GetPropertyNoGC(cx, boxed, receiver, name->asPropertyName(), res.address())) break; } } - JSAtom* name = ToAtom<CanGC>(cx, key); - if (!name) + RootedId id(cx); + if (!ToPropertyKey(cx, key, &id)) return false; - - if (name->isIndex(&index)) { - if (!GetElement(cx, boxed, receiver, index, res)) - return false; - } else { - if (!GetProperty(cx, boxed, receiver, name->asPropertyName(), res)) - return false; - } + if (!GetProperty(cx, boxed, boxed, id, res)) + return false; } while (false); // Note: we don't call a __noSuchMethod__ hook when |this| was primitive. assertSameCompartmentDebugOnly(cx, res); return true; } @@ -779,17 +761,17 @@ TypeOfObjectOperation(JSObject* obj, JSR static MOZ_ALWAYS_INLINE bool InitElemOperation(JSContext* cx, HandleObject obj, HandleValue idval, HandleValue val) { MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE)); MOZ_ASSERT(!obj->getClass()->getProperty); MOZ_ASSERT(!obj->getClass()->setProperty); RootedId id(cx); - if (!ValueToId<CanGC>(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; return DefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE); } static MOZ_ALWAYS_INLINE bool InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue val) {
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -2019,17 +2019,17 @@ CASE(JSOP_AND) bool cond = ToBoolean(REGS.stackHandleAt(-1)); if (!cond) ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc)); } END_CASE(JSOP_AND) #define FETCH_ELEMENT_ID(n, id) \ JS_BEGIN_MACRO \ - if (!ValueToId<CanGC>(cx, REGS.stackHandleAt(n), &(id))) \ + if (!ToPropertyKey(cx, REGS.stackHandleAt(n), &(id))) \ goto error; \ JS_END_MACRO #define TRY_BRANCH_AFTER_COND(cond,spdec) \ JS_BEGIN_MACRO \ MOZ_ASSERT(js_CodeSpec[*REGS.pc].length == 1); \ unsigned diff_ = (unsigned) GET_UINT8(REGS.pc) - (unsigned) JSOP_IFEQ; \ if (diff_ <= 1) { \ @@ -2463,17 +2463,17 @@ CASE(JSOP_STRICTDELELEM) /* Fetch the left part and resolve it to a non-null object. */ ReservedRooted<JSObject*> obj(&rootObject0); FETCH_OBJECT(cx, -2, obj); ReservedRooted<Value> propval(&rootValue0, REGS.sp[-1]); ObjectOpResult result; ReservedRooted<jsid> id(&rootId0); - if (!ValueToId<CanGC>(cx, propval, &id)) + if (!ToPropertyKey(cx, propval, &id)) goto error; if (!DeleteProperty(cx, obj, id, result)) goto error; if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELELEM) { result.reportError(cx, obj, id); goto error; } @@ -4260,17 +4260,17 @@ template <bool strict> bool js::DeleteElementJit(JSContext* cx, HandleValue val, HandleValue index, bool* bp) { RootedObject obj(cx, ToObjectFromStack(cx, val)); if (!obj) return false; RootedId id(cx); - if (!ValueToId<CanGC>(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; ObjectOpResult result; if (!DeleteProperty(cx, obj, id, result)) return false; if (strict) { if (!result) return result.reportError(cx, obj, id); @@ -4296,29 +4296,29 @@ js::CallElement(JSContext* cx, MutableHa return GetElementOperation(cx, JSOP_CALLELEM, lref, rref, res); } bool js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value, bool strict) { RootedId id(cx); - if (!ValueToId<CanGC>(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, receiver, id, value, strict); } bool js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value, bool strict, HandleScript script, jsbytecode* pc) { MOZ_ASSERT(pc); RootedId id(cx); - if (!ValueToId<CanGC>(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, receiver, id, value, strict, script, pc); } bool js::InitElementArray(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue value) { @@ -4481,17 +4481,17 @@ js::InitGetterSetterOperation(JSContext* return InitGetterSetterOperation(cx, pc, obj, id, val); } bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval, HandleObject val) { RootedId id(cx); - if (!ValueToId<CanGC>(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; return InitGetterSetterOperation(cx, pc, obj, id, val); } bool js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv, HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res)
--- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2018,22 +2018,25 @@ 1234567890123456789012345678901234567890 * Category: Variables and Scopes * Type: Arguments * Operands: * Stack: => rest */ \ macro(JSOP_REST, 224, "rest", NULL, 1, 0, 1, JOF_BYTE|JOF_TYPESET) \ \ /* - * Pops the top of stack value, converts it into a jsid (int or string), and - * pushes it onto the stack. + * First, throw a TypeError if baseValue is null or undefined. Then, + * replace the top-of-stack value propertyNameValue with + * ToPropertyKey(propertyNameValue). This opcode implements ES6 12.3.2.1 + * steps 7-10. It is also used to implement computed property names; in + * that case, baseValue is always an object, so the first step is a no-op. * Category: Literals * Type: Object * Operands: - * Stack: obj, id => obj, (jsid of id) + * Stack: baseValue, propertyNameValue => baseValue, propertyKey */ \ macro(JSOP_TOID, 225, "toid", NULL, 1, 1, 1, JOF_BYTE) \ \ /* * Pushes the implicit 'this' value for calls to the associated name onto * the stack. * Category: Variables and Scopes * Type: This
--- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -1057,18 +1057,19 @@ public: mContainerReferenceFrame = const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() : mBuilder->FindReferenceFrameFor(mContainerFrame)); bool isAtRoot = !aContainerItem || (aContainerItem->Frame() == mBuilder->RootReferenceFrame()); MOZ_ASSERT_IF(isAtRoot, mContainerReferenceFrame == mBuilder->RootReferenceFrame()); mContainerAnimatedGeometryRoot = isAtRoot ? mContainerReferenceFrame : nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder); - MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(), - mContainerAnimatedGeometryRoot)); + MOZ_ASSERT(!mBuilder->IsPaintingToWindow() || + nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(), + mContainerAnimatedGeometryRoot)); NS_ASSERTION(!aContainerItem || !aContainerItem->ShouldFixToViewport(mBuilder), "Container items never return true for ShouldFixToViewport"); mContainerFixedPosFrame = FindFixedPosFrameForLayerData(mContainerAnimatedGeometryRoot, false); // When AllowResidualTranslation is false, display items will be drawn // scaled with a translation by integer pixels, so we know how the snapping // will work. mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() && @@ -2114,18 +2115,17 @@ ContainerState::GetLayerCreationHint(con // Check whether the layer will be scrollable. This is used as a hint to // influence whether tiled layers are used or not. // Check whether there's any active scroll frame on the animated geometry // root chain. nsIFrame* fParent; for (const nsIFrame* f = aAnimatedGeometryRoot; f != mContainerAnimatedGeometryRoot; - f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, - fParent, mContainerAnimatedGeometryRoot)) { + f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, fParent)) { fParent = nsLayoutUtils::GetCrossDocParentFrame(f); if (!fParent) { break; } nsIScrollableFrame* scrollable = do_QueryFrame(fParent); if (scrollable #ifdef MOZ_B2G && scrollable->WantAsyncScroll() @@ -2796,28 +2796,28 @@ PaintedLayerDataTree::GetParentAnimatedG MOZ_ASSERT(aAnimatedGeometryRoot); MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), aAnimatedGeometryRoot)); if (aAnimatedGeometryRoot == Builder()->RootReferenceFrame()) { return nullptr; } nsIFrame* agr = Builder()->FindAnimatedGeometryRootFor( - const_cast<nsIFrame*>(aAnimatedGeometryRoot), Builder()->RootReferenceFrame()); + const_cast<nsIFrame*>(aAnimatedGeometryRoot)); MOZ_ASSERT_IF(agr, nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), agr)); if (agr != aAnimatedGeometryRoot) { return agr; } // aAnimatedGeometryRoot is its own animated geometry root. // Find the animated geometry root for its cross-doc parent frame. nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot); if (!parent) { return nullptr; } - return Builder()->FindAnimatedGeometryRootFor(parent, Builder()->RootReferenceFrame()); + return Builder()->FindAnimatedGeometryRootFor(parent); } void PaintedLayerDataTree::Finish() { if (mRoot) { mRoot->Finish(false); } @@ -3786,18 +3786,17 @@ IsCaretWithCustomClip(nsDisplayItem* aIt static DisplayItemClip GetScrollClipIntersection(nsDisplayListBuilder* aBuilder, const nsIFrame* aAnimatedGeometryRoot, const nsIFrame* aStopAtAnimatedGeometryRoot, bool aIsCaret) { DisplayItemClip resultClip; nsIFrame* fParent; for (const nsIFrame* f = aAnimatedGeometryRoot; f != aStopAtAnimatedGeometryRoot; - f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, - fParent, aStopAtAnimatedGeometryRoot)) { + f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, fParent)) { fParent = nsLayoutUtils::GetCrossDocParentFrame(f); if (!fParent) { // This means aStopAtAnimatedGeometryRoot was not an ancestor // of aAnimatedGeometryRoot. This is a weird case but it // can happen, e.g. when a scrolled frame contains a frame with opacity // which contains a frame that is not scrolled by the scrolled frame. // For now, we just don't apply any specific scroll clip to this layer. return DisplayItemClip(); @@ -3900,18 +3899,18 @@ ContainerState::ProcessDisplayItems(nsDi animatedGeometryRoot = lastAnimatedGeometryRoot; } else { forceInactive = false; if (mManager->IsWidgetLayerManager()) { animatedGeometryRoot = realAnimatedGeometryRootOfItem; // Unlike GetAnimatedGeometryRootFor(), GetAnimatedGeometryRootForFrame() does not // take ShouldFixToViewport() into account, so it will return something different // for fixed background items. - animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootForFrame( - mBuilder, item->Frame(), item->ReferenceFrame()); + animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootFor( + item, mBuilder, nsLayoutUtils::AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED); } else { // For inactive layer subtrees, splitting content into PaintedLayers // based on animated geometry roots is pointless. It's more efficient // to build the minimum number of layers. animatedGeometryRoot = mContainerAnimatedGeometryRoot; } if (animatedGeometryRoot != lastAnimatedGeometryRoot) { @@ -4738,34 +4737,39 @@ void ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) { if (mFlattenToSingleLayer) { // animated geometry roots are forced to all match, so we can't // use them and we don't get async scrolling. return; } + if (!mBuilder->IsPaintingToWindow()) { + // async scrolling not possible, and async scrolling info not computed + // for this paint. + return; + } + nsAutoTArray<FrameMetrics,2> metricsArray; if (aEntry->mBaseFrameMetrics) { metricsArray.AppendElement(*aEntry->mBaseFrameMetrics); // The base FrameMetrics was not computed by the nsIScrollableframe, so it // should not have a mask layer. MOZ_ASSERT(!aEntry->mBaseFrameMetrics->GetMaskLayerIndex()); } uint32_t baseLength = metricsArray.Length(); // Any extra mask layers we need to attach to FrameMetrics. nsTArray<RefPtr<Layer>> maskLayers; nsIFrame* fParent; for (const nsIFrame* f = aEntry->mAnimatedGeometryRootForScrollMetadata; f != mContainerAnimatedGeometryRoot; - f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(this->mBuilder, - fParent, mContainerAnimatedGeometryRoot)) { + f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(this->mBuilder, fParent)) { fParent = nsLayoutUtils::GetCrossDocParentFrame(f); if (!fParent) { // This means mContainerAnimatedGeometryRoot was not an ancestor // of aEntry->mAnimatedGeometryRoot. This is a weird case but it // can happen, e.g. when a scrolled frame contains a frame with opacity // which contains a frame that is not scrolled by the scrolled frame. // For now, we just don't apply any specific async scrolling to this layer. // It will async-scroll with mContainerAnimatedGeometryRoot, which @@ -4899,18 +4903,17 @@ ContainerState::PostprocessRetainedLayer PaintedLayer* p = e->mLayer->AsPaintedLayer(); if (p) { InvalidateVisibleBoundsChangesForScrolledLayer(p); } if (!e->mOpaqueRegion.IsEmpty()) { const nsIFrame* animatedGeometryRootToCover = animatedGeometryRootForOpaqueness; if (e->mOpaqueForAnimatedGeometryRootParent && - nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent(), - mContainerAnimatedGeometryRoot) + nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent()) == mContainerAnimatedGeometryRoot) { animatedGeometryRootToCover = mContainerAnimatedGeometryRoot; data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover, e->mFixedPosFrameForLayerData); } if (!data) { if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot &&
--- a/layout/base/FrameLayerBuilder.h +++ b/layout/base/FrameLayerBuilder.h @@ -56,39 +56,42 @@ struct ContainerLayerParameters { : mXScale(1) , mYScale(1) , mLayerContentsVisibleRect(nullptr) , mBackgroundColor(NS_RGBA(0,0,0,0)) , mInTransformedSubtree(false) , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale) : mXScale(aXScale) , mYScale(aYScale) , mLayerContentsVisibleRect(nullptr) , mBackgroundColor(NS_RGBA(0,0,0,0)) , mInTransformedSubtree(false) , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale, const nsIntPoint& aOffset, const ContainerLayerParameters& aParent) : mXScale(aXScale) , mYScale(aYScale) , mLayerContentsVisibleRect(nullptr) , mOffset(aOffset) , mBackgroundColor(aParent.mBackgroundColor) , mInTransformedSubtree(aParent.mInTransformedSubtree) , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree) , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants) , mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort) + , mForEventsOnly(aParent.mForEventsOnly) {} float mXScale, mYScale; LayoutDeviceToLayerScale2D Scale() const { return LayoutDeviceToLayerScale2D(mXScale, mYScale); } @@ -107,16 +110,17 @@ struct ContainerLayerParameters { return LayerIntPoint::FromUntyped(mOffset); } nscolor mBackgroundColor; bool mInTransformedSubtree; bool mInActiveTransformedSubtree; bool mDisableSubpixelAntialiasingInDescendants; bool mInLowPrecisionDisplayPort; + bool mForEventsOnly; /** * When this is false, PaintedLayer coordinates are drawn to with an integer * translation and the scale in mXScale/mYScale. */ bool AllowResidualTranslation() { // If we're in a transformed subtree, but no ancestor transform is actively // changing, we'll use the residual translation when drawing into the
--- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -5579,49 +5579,55 @@ nsContextBoxBlur::InsetBoxBlur(gfxContex RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect, Point aShadowOffset) { if (aDestinationRect.IsEmpty()) { mContext = nullptr; return false; } + gfxContextAutoSaveRestore autoRestore(aDestinationCtx); + IntSize blurRadius; IntSize spreadRadius; // Convert the blur and spread radius to device pixels bool constrainSpreadRadius = false; GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel, aBlurRadiusAppUnits, aSpreadDistanceAppUnits, blurRadius, spreadRadius, constrainSpreadRadius); // The blur and spread radius are scaled already, so scale all // input data to the blur. This way, we don't have to scale the min // inset blur to the invert of the dest context, then rescale it back // when we draw to the destination surface. gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true); - Matrix currentMatrix = ToMatrix(aDestinationCtx->CurrentMatrix()); - - Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect); - Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect); - Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect); + Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix()); + + // XXX: we could probably handle negative scales but for now it's easier just to fallback + if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) { + // If we don't have a rotation, we're pre-transforming all the rects. + aDestinationCtx->SetMatrix(gfxMatrix()); + } else { + // Don't touch anything, we have a rotation. + transform = Matrix(); + } + + Rect transformedDestRect = transform.TransformBounds(aDestinationRect); + Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect); + Rect transformedSkipRect = transform.TransformBounds(aSkipRect); transformedDestRect.Round(); transformedShadowClipRect.Round(); transformedSkipRect.RoundIn(); for (size_t i = 0; i < 4; i++) { aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width); aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height); } - { - gfxContextAutoSaveRestore autoRestore(aDestinationCtx); - aDestinationCtx->SetMatrix(gfxMatrix()); - - mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, - transformedShadowClipRect, - blurRadius, spreadRadius, - aShadowColor, aHasBorderRadius, - aInnerClipRectRadii, transformedSkipRect, - aShadowOffset); - } + mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, + transformedShadowClipRect, + blurRadius, spreadRadius, + aShadowColor, aHasBorderRadius, + aInnerClipRectRadii, transformedSkipRect, + aShadowOffset); return true; }
--- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1036,16 +1036,19 @@ nsDisplayListBuilder::IsAnimatedGeometry if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame)) return true; if (!aFrame->GetParent() && nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext())) { // Viewport frames in a display port need to be animated geometry roots // for background-attachment:fixed elements. return true; } + if (aFrame->IsTransformed()) { + return true; + } nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); if (!parent) return true; nsIAtom* parentType = parent->GetType(); // Treat the slider thumb as being as an active scrolled root when it wants // its own layer so that it can move without repainting. @@ -1074,69 +1077,65 @@ nsDisplayListBuilder::IsAnimatedGeometry if (aParent) { *aParent = parent; } return false; } bool nsDisplayListBuilder::GetCachedAnimatedGeometryRoot(const nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor, nsIFrame** aOutResult) { - AnimatedGeometryRootLookup lookup(aFrame, aStopAtAncestor); - return mAnimatedGeometryRootCache.Get(lookup, aOutResult); + return mAnimatedGeometryRootCache.Get(const_cast<nsIFrame*>(aFrame), aOutResult); } static nsIFrame* ComputeAnimatedGeometryRootFor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor = nullptr, bool aUseCache = false) { nsIFrame* cursor = aFrame; - while (cursor != aStopAtAncestor) { + while (cursor != aBuilder->RootReferenceFrame()) { if (aUseCache) { nsIFrame* result; - if (aBuilder->GetCachedAnimatedGeometryRoot(cursor, aStopAtAncestor, &result)) { + if (aBuilder->GetCachedAnimatedGeometryRoot(cursor, &result)) { return result; } } nsIFrame* next; if (aBuilder->IsAnimatedGeometryRoot(cursor, &next)) return cursor; cursor = next; } return cursor; } nsIFrame* -nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame, const nsIFrame* aStopAtAncestor) +nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame) { if (aFrame == mCurrentFrame) { return mCurrentAnimatedGeometryRoot; } - nsIFrame* result = ComputeAnimatedGeometryRootFor(this, aFrame, aStopAtAncestor, true); - AnimatedGeometryRootLookup lookup(aFrame, aStopAtAncestor); - mAnimatedGeometryRootCache.Put(lookup, result); + nsIFrame* result = ComputeAnimatedGeometryRootFor(this, aFrame, true); + mAnimatedGeometryRootCache.Put(aFrame, result); return result; } void nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot() { // technically we only need to clear any part of the cache that relies on // the AGR of mCurrentFrame (i.e. all entries in mAnimatedGeometryRootCache // where the key frame is a descendant of mCurrentFrame) but doing that is // complicated so we just clear the whole thing. mAnimatedGeometryRootCache.Clear(); mCurrentAnimatedGeometryRoot = ComputeAnimatedGeometryRootFor(this, const_cast<nsIFrame *>(mCurrentFrame)); - AnimatedGeometryRootLookup lookup(mCurrentFrame, nullptr); - mAnimatedGeometryRootCache.Put(lookup, mCurrentAnimatedGeometryRoot); + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), mCurrentAnimatedGeometryRoot)); + mAnimatedGeometryRootCache.Put(const_cast<nsIFrame*>(mCurrentFrame), mCurrentAnimatedGeometryRoot); } void nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) { if (!mWindowDraggingAllowed || !IsForPainting()) { return; } @@ -3277,16 +3276,22 @@ nsDisplayLayerEventRegions::AddFrame(nsD mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox); } else if (aFrame->GetType() == nsGkAtoms::objectFrame) { // If the frame is a plugin frame and wants to handle wheel events as // default action, we should add the frame to dispatch-to-content region. nsPluginFrame* pluginFrame = do_QueryFrame(aFrame); if (pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction()) { mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox); } + } else if (gfxPlatform::GetPlatform()->SupportsApzWheelInput() && + nsLayoutUtils::IsScrollFrameWithSnapping(aFrame->GetParent())) { + // If the frame is the inner content of a scrollable frame with snap-points + // then we want to handle wheel events for it on the main thread. Add it to + // the d-t-c region so that APZ waits for the main thread. + mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox); } // Touch action region uint32_t touchAction = nsLayoutUtils::GetTouchActionFromFrame(aFrame); if (touchAction & NS_STYLE_TOUCH_ACTION_NONE) { mNoActionRegion.Or(mNoActionRegion, borderBox); } else { @@ -3927,19 +3932,21 @@ nsRegion nsDisplayOpacity::GetOpaqueRegi return nsRegion(); } // nsDisplayOpacity uses layers for rendering already_AddRefed<Layer> nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters params = aContainerParameters; + params.mForEventsOnly = mForEventsOnly; RefPtr<Layer> container = aManager->GetLayerBuilder()-> BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList, - aContainerParameters, nullptr, + params, nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); if (!container) return nullptr; container->SetOpacity(mOpacity); nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder, this, mFrame, eCSSProperty_opacity); @@ -4775,16 +4782,19 @@ nsDisplayTransform::nsDisplayTransform(n MOZ_COUNT_CTOR(nsDisplayTransform); MOZ_ASSERT(aFrame, "Must have a frame!"); Init(aBuilder); } void nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame); mReferenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame); mVisibleRect = aBuilder->GetDirtyRect() + mToReferenceFrame; } void
--- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -255,17 +255,17 @@ public: * returning the next ancestor to check. */ bool IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent = nullptr); /** * Returns the nearest ancestor frame to aFrame that is considered to have * (or will have) animated geometry. This can return aFrame. */ - nsIFrame* FindAnimatedGeometryRootFor(nsIFrame* aFrame, const nsIFrame* aStopAtAncestor = nullptr); + nsIFrame* FindAnimatedGeometryRootFor(nsIFrame* aFrame); /** * @return the root of the display list's frame (sub)tree, whose origin * establishes the coordinate system for the display list */ nsIFrame* RootReferenceFrame() { return mReferenceFrame; @@ -639,21 +639,21 @@ public: aBuilder->FindReferenceFrameFor(aForChild, &aBuilder->mCurrentOffsetToReferenceFrame); } if (aBuilder->mCurrentFrame == aForChild->GetParent()) { if (aBuilder->IsAnimatedGeometryRoot(aForChild)) { aBuilder->mCurrentAnimatedGeometryRoot = aForChild; } } else { - // Stop at the previous animated geometry root to help cases that - // aren't immediate descendents. aBuilder->mCurrentAnimatedGeometryRoot = - aBuilder->FindAnimatedGeometryRootFor(aForChild, aBuilder->mCurrentAnimatedGeometryRoot); + aBuilder->FindAnimatedGeometryRootFor(aForChild); } + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(), + aBuilder->mCurrentAnimatedGeometryRoot)); aBuilder->mCurrentFrame = aForChild; aBuilder->mDirtyRect = aDirtyRect; aBuilder->mIsAtRootOfPseudoStackingContext = aIsRoot; } void SetDirtyRect(const nsRect& aRect) { mBuilder->mDirtyRect = aRect; } void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame, const nsPoint& aOffset) { @@ -971,22 +971,21 @@ public: * This will add the current frame to the will-change budget the first * time it is seen. On subsequent calls this will return the same * answer. This effectively implements a first-come, first-served * allocation of the will-change budget. */ bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize); /** - * Look up the cached animated geometry root for aFrame subject to - * aStopAtAncestor. Store the nsIFrame* result into *aOutResult, and return - * true if the cache was hit. Return false if the cache was not hit. + * Look up the cached animated geometry root for aFrame subject Store the + * nsIFrame* result into *aOutResult, and return true if the cache was hit. + * Return false if the cache was not hit. */ bool GetCachedAnimatedGeometryRoot(const nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor, nsIFrame** aOutResult); void SetCommittedScrollInfoItemList(nsDisplayList* aScrollInfoItemStorage) { mCommittedScrollInfoItems = aScrollInfoItemStorage; } nsDisplayList* CommittedScrollInfoItems() const { return mCommittedScrollInfoItems; } @@ -1108,37 +1107,18 @@ private: const nsIFrame* mCurrentFrame; // The reference frame for mCurrentFrame. const nsIFrame* mCurrentReferenceFrame; // The offset from mCurrentFrame to mCurrentReferenceFrame. nsPoint mCurrentOffsetToReferenceFrame; // The animated geometry root for mCurrentFrame. nsIFrame* mCurrentAnimatedGeometryRoot; - struct AnimatedGeometryRootLookup { - const nsIFrame* mFrame; - const nsIFrame* mStopAtFrame; - - AnimatedGeometryRootLookup(const nsIFrame* aFrame, const nsIFrame* aStopAtFrame) - : mFrame(aFrame) - , mStopAtFrame(aStopAtFrame) - { - } - - PLDHashNumber Hash() const { - return mozilla::HashBytes(this, sizeof(*this)); - } - - bool operator==(const AnimatedGeometryRootLookup& aOther) const { - return mFrame == aOther.mFrame && mStopAtFrame == aOther.mStopAtFrame; - } - }; // Cache for storing animated geometry roots for arbitrary frames - nsDataHashtable<nsGenericHashKey<AnimatedGeometryRootLookup>, nsIFrame*> - mAnimatedGeometryRootCache; + nsDataHashtable<nsPtrHashKey<nsIFrame>, nsIFrame*> mAnimatedGeometryRootCache; // will-change budget tracker nsDataHashtable<nsPtrHashKey<nsPresContext>, DocumentWillChangeBudget> mWillChangeBudget; // Any frame listed in this set is already counted in the budget // and thus is in-budget. nsTHashtable<nsPtrHashKey<nsIFrame> > mBudgetSet; @@ -3954,16 +3934,25 @@ public: * context. */ bool IsLeafOf3DContext() { return (IsTransformSeparator() || (!mFrame->Extend3DContext() && mFrame->Combines3DTransformWithAncestors())); } + /** + * Whether this transform item forms a reference frame boundary. + * In other words, the reference frame of the contained items is our frame, + * and the reference frame of this item is some ancestor of our frame. + */ + bool IsReferenceFrameBoundary() { + return !mTransformGetter && !mIsTransformSeparator; + } + private: void ComputeBounds(nsDisplayListBuilder* aBuilder); void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder); void Init(nsDisplayListBuilder* aBuilder); static Matrix4x4 GetResultingTransformMatrixInternal(const FrameTransformProperties& aProperties, const nsPoint& aOrigin, float aAppUnitsPerPixel,
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1386,22 +1386,27 @@ nsLayoutUtils::GetAfterFrameForContent(n /*static*/ nsIFrame* nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame) { return GetAfterFrameForContent(aFrame, aFrame->GetContent()); } // static nsIFrame* -nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType) +nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, + nsIAtom* aFrameType, + nsIFrame* aStopAt) { for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { if (frame->GetType() == aFrameType) { return frame; } + if (frame == aStopAt) { + break; + } } return nullptr; } // static nsIFrame* nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame) { @@ -1861,39 +1866,48 @@ nsLayoutUtils::SetScrollbarThumbLayeriza bool nsLayoutUtils::IsScrollbarThumbLayerized(nsIFrame* aThumbFrame) { return reinterpret_cast<intptr_t>(aThumbFrame->Properties().Get(ScrollbarThumbLayerized())); } nsIFrame* nsLayoutUtils::GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor) -{ - return aBuilder->FindAnimatedGeometryRootFor(aFrame, aStopAtAncestor); + nsIFrame* aFrame) +{ + return aBuilder->FindAnimatedGeometryRootFor(aFrame); } nsIFrame* nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem, - nsDisplayListBuilder* aBuilder) + nsDisplayListBuilder* aBuilder, + uint32_t aFlags) { nsIFrame* f = aItem->Frame(); - if (aItem->ShouldFixToViewport(aBuilder)) { + if (!(aFlags & AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED) && + aItem->ShouldFixToViewport(aBuilder)) { // Make its active scrolled root be the active scrolled root of // the enclosing viewport, since it shouldn't be scrolled by scrolled // frames in its document. InvalidateFixedBackgroundFramesFromList in // nsGfxScrollFrame will not repaint this item when scrolling occurs. nsIFrame* viewportFrame = - nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame); - NS_ASSERTION(viewportFrame, "no viewport???"); - return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame, - aBuilder->FindReferenceFrameFor(viewportFrame)); - } - return GetAnimatedGeometryRootForFrame(aBuilder, f, aItem->ReferenceFrame()); + nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame, aBuilder->RootReferenceFrame()); + if (viewportFrame) { + return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame); + } + } + if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(aItem)->IsReferenceFrameBoundary() && + f != aBuilder->RootReferenceFrame()) { + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(f); + if (parent) { + return GetAnimatedGeometryRootForFrame(aBuilder, parent); + } + } + return GetAnimatedGeometryRootForFrame(aBuilder, f); } // static nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrameForDirection(nsIFrame* aFrame, Direction aDirection) { NS_ASSERTION(aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame"); @@ -4527,20 +4541,25 @@ nsLayoutUtils::IntrinsicForAxis(Physical AutoMaybeDisableFontInflation an(aFrame); // We want the size this frame will contribute to the parent's inline-size, // so we work in the parent's writing mode; but if aFrame is orthogonal to // its parent, we'll need to look at its BSize instead of min/pref-ISize. const nsStylePosition* stylePos = aFrame->StylePosition(); uint8_t boxSizing = stylePos->mBoxSizing; - const nsStyleCoord& styleISize = - horizontalAxis ? stylePos->mWidth : stylePos->mHeight; const nsStyleCoord& styleMinISize = horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight; + const nsStyleCoord& styleISize = + (aFlags & MIN_INTRINSIC_ISIZE) ? styleMinISize : + (horizontalAxis ? stylePos->mWidth : stylePos->mHeight); + MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || + styleISize.GetUnit() == eStyleUnit_Auto || + styleISize.GetUnit() == eStyleUnit_Enumerated, + "should only use MIN_INTRINSIC_ISIZE for intrinsic values"); const nsStyleCoord& styleMaxISize = horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight; // We build up two values starting with the content box, and then // adding padding, border and margin. The result is normally // |result|. Then, when we handle 'width', 'min-width', and // 'max-width', we use the results we've been building in |min| as a // minimum, overriding 'min-width'. This ensures two things: @@ -4746,51 +4765,86 @@ nsLayoutUtils::IntrinsicForContainer(nsR /* static */ nscoord nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, nsRenderingContext* aRC, nsIFrame* aFrame, IntrinsicISizeType aType, uint32_t aFlags) { - NS_PRECONDITION(aFrame, "null frame"); - NS_PRECONDITION(aFrame->GetParent(), - "MinSizeContributionForAxis called on frame not in tree"); + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aFrame->IsFlexOrGridItem(), + "only grid/flex items have this behavior currently"); #ifdef DEBUG_INTRINSIC_WIDTH nsFrame::IndentBy(stderr, gNoiseIndent); static_cast<nsFrame*>(aFrame)->ListTag(stderr); printf_stderr(" %s min-isize for %s WM:\n", aType == MIN_ISIZE ? "min" : "pref", aWM.IsVertical() ? "vertical" : "horizontal"); #endif + const nsStylePosition* const stylePos = aFrame->StylePosition(); + const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth + : &stylePos->mMinHeight; + nscoord minSize; + nscoord* fixedMinSize = nullptr; + auto minSizeUnit = style->GetUnit(); + if (minSizeUnit == eStyleUnit_Auto) { + if (aFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) { + style = aAxis == eAxisHorizontal ? &stylePos->mWidth + : &stylePos->mHeight; + if (GetAbsoluteCoord(*style, minSize)) { + // We have a definite width/height. This is the "specified size" in: + // https://drafts.csswg.org/css-grid/#min-size-auto + fixedMinSize = &minSize; + } + // XXX the "transferred size" piece is missing (bug 1218178) + } else { + // min-[width|height]:auto with overflow != visible computes to zero. + minSize = 0; + fixedMinSize = &minSize; + } + } else if (GetAbsoluteCoord(*style, minSize)) { + fixedMinSize = &minSize; + } else if (minSizeUnit != eStyleUnit_Enumerated) { + MOZ_ASSERT(style->HasPercent()); + minSize = 0; + fixedMinSize = &minSize; + } + + if (!fixedMinSize) { + // Let the caller deal with the "content size" cases. +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast<nsFrame*>(aFrame)->ListTag(stderr); + printf_stderr(" %s min-isize is indefinite.\n", + aType == MIN_ISIZE ? "min" : "pref"); +#endif + return NS_UNCONSTRAINEDSIZE; + } + // If aFrame is a container for font size inflation, then shrink // wrapping inside of it should not apply font size inflation. AutoMaybeDisableFontInflation an(aFrame); PhysicalAxis ourInlineAxis = aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline); nsIFrame::IntrinsicISizeOffsetData offsets = ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets() : aFrame->IntrinsicBSizeOffsets(); nscoord result = 0; nscoord min = 0; - const nsStylePosition* stylePos = aFrame->StylePosition(); - uint8_t boxSizing = stylePos->mBoxSizing; - const nsStyleCoord& style = aAxis == eAxisHorizontal ? stylePos->mMinWidth - : stylePos->mMinHeight; - nscoord minSize; - nscoord* fixedMinSize = nullptr; - if (GetAbsoluteCoord(style, minSize)) { - fixedMinSize = &minSize; - } - result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, boxSizing, - result, min, style, fixedMinSize, - style, fixedMinSize, style, aFlags, aAxis); + + const nsStyleCoord& maxISize = + aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight; + result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, + stylePos->mBoxSizing, + result, min, *style, fixedMinSize, + *style, nullptr, maxISize, aFlags, aAxis); #ifdef DEBUG_INTRINSIC_WIDTH nsFrame::IndentBy(stderr, gNoiseIndent); static_cast<nsFrame*>(aFrame)->ListTag(stderr); printf_stderr(" %s min-isize is %d twips.\n", aType == MIN_ISIZE ? "min" : "pref", result); #endif @@ -8657,8 +8711,20 @@ nsLayoutUtils::GetSelectionBoundingRect( true, false); } res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect : accumulator.mResultRect; } return res; } + +/* static */ bool +nsLayoutUtils::IsScrollFrameWithSnapping(nsIFrame* aFrame) +{ + nsIScrollableFrame* sf = do_QueryFrame(aFrame); + if (!sf) { + return false; + } + ScrollbarStyles styles = sf->GetScrollbarStyles(); + return styles.mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE || + styles.mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE; +}
--- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -268,20 +268,23 @@ public: static nsIFrame* GetAfterFrame(nsIFrame* aFrame); /** * Given a frame, search up the frame tree until we find an * ancestor that (or the frame itself) is of type aFrameType, if any. * * @param aFrame the frame to start at * @param aFrameType the frame type to look for + * @param aStopAt a frame to stop at after we checked it * @return a frame of the given type or nullptr if no * such ancestor exists */ - static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType); + static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame, + nsIAtom* aFrameType, + nsIFrame* aStopAt = nullptr); /** * Given a frame, search up the frame tree until we find an * ancestor that (or the frame itself) is a "Page" frame, if any. * * @param aFrame the frame to start at * @return a frame of type nsGkAtoms::pageFrame or nullptr if no * such ancestor exists @@ -544,27 +547,34 @@ public: * Frames with different active geometry roots are in different PaintedLayers, * so that we can animate the geometry root by changing its transform (either * on the main thread or in the compositor). * The animated geometry root is required to be a descendant (or equal to) * aItem's ReferenceFrame(), which means that we will fall back to * returning aItem->ReferenceFrame() when we can't find another animated * geometry root. */ + enum { + /** + * If the AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED flag is set, then we + * do not do any special processing for background attachment fixed items, + * instead treating them like any other frame. + */ + AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED = 0x01 + }; static nsIFrame* GetAnimatedGeometryRootFor(nsDisplayItem* aItem, - nsDisplayListBuilder* aBuilder); + nsDisplayListBuilder* aBuilder, + uint32_t aFlags = 0); /** * Finds the nearest ancestor frame to aFrame that is considered to have (or - * will have) "animated geometry". This could be aFrame. Returns - * aStopAtAncestor if no closer ancestor is found. + * will have) "animated geometry". This could be aFrame. */ static nsIFrame* GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor); + nsIFrame* aFrame); /** * GetScrollableFrameFor returns the scrollable frame for a scrolled frame */ static nsIScrollableFrame* GetScrollableFrameFor(const nsIFrame *aScrolledFrame); /** * GetNearestScrollableFrameForDirection locates the first ancestor of @@ -1309,39 +1319,52 @@ public: /** * Get the contribution of aFrame to its containing block's intrinsic * size for the given physical axis. This considers the child's intrinsic * width, its 'width', 'min-width', and 'max-width' properties (or 'height' * variations if that's what matches aAxis) and its padding, border and margin * in the corresponding dimension. */ - enum IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE }; + enum class IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE }; + static const auto MIN_ISIZE = IntrinsicISizeType::MIN_ISIZE; + static const auto PREF_ISIZE = IntrinsicISizeType::PREF_ISIZE; enum { IGNORE_PADDING = 0x01, BAIL_IF_REFLOW_NEEDED = 0x02, // returns NS_INTRINSIC_WIDTH_UNKNOWN if so + MIN_INTRINSIC_ISIZE = 0x04, // use min-width/height instead of width/height }; static nscoord IntrinsicForAxis(mozilla::PhysicalAxis aAxis, nsRenderingContext* aRenderingContext, nsIFrame* aFrame, IntrinsicISizeType aType, uint32_t aFlags = 0); /** * Calls IntrinsicForAxis with aFrame's parent's inline physical axis. */ static nscoord IntrinsicForContainer(nsRenderingContext* aRenderingContext, nsIFrame* aFrame, IntrinsicISizeType aType, uint32_t aFlags = 0); /** - * Get the contribution of aFrame for the given physical axis. + * Get the definite size contribution of aFrame for the given physical axis. * This considers the child's 'min-width' property (or 'min-height' if the * given axis is vertical), and its padding, border, and margin in the - * corresponding dimension. + * corresponding dimension. If the 'min-' property is 'auto' (and 'overflow' + * is 'visible') and the corresponding 'width'/'height' is definite it returns + * the "specified / transferred size" for: + * https://drafts.csswg.org/css-grid/#min-size-auto + * Note that any percentage in 'width'/'height' makes it count as indefinite. + * If the 'min-' property is 'auto' and 'overflow' is not 'visible', then it + * calculates the result as if the 'min-' computed value is zero. + * Otherwise, return NS_UNCONSTRAINEDSIZE. + * + * @note this behavior is specific to Grid/Flexbox (currently) so aFrame + * should be a grid/flex item. */ static nscoord MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis, nsRenderingContext* aRC, nsIFrame* aFrame, IntrinsicISizeType aType, uint32_t aFlags = 0); /** @@ -2758,16 +2781,22 @@ public: /** * Takes a selection, and returns selection's bounding rect which is relative * to its root frame. * * @param aSel Selection to check */ static nsRect GetSelectionBoundingRect(mozilla::dom::Selection* aSel); + + /** + * Returns true if the given frame is a scrollframe and it has snap points. + */ + static bool IsScrollFrameWithSnapping(nsIFrame* aFrame); + private: static uint32_t sFontSizeInflationEmPerLine; static uint32_t sFontSizeInflationMinTwips; static uint32_t sFontSizeInflationLineThreshold; static int32_t sFontSizeInflationMappingIntercept; static uint32_t sFontSizeInflationMaxRatio; static bool sFontSizeInflationForceEnabled; static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1521,17 +1521,17 @@ nsFlexContainerFrame:: 0, 0, flags); aFlexItem.SetHadMeasuringReflow(); // If this is the first child, save its ascent, since it may be what // establishes the container's baseline. Also save the ascent if this child // needs to be baseline-aligned. (Else, we don't care about ascent/baseline.) if (aFlexItem.Frame() == mFrames.FirstChild() || - aFlexItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE) { + aFlexItem.GetAlignSelf() == NS_STYLE_ALIGN_BASELINE) { aFlexItem.SetAscent(childDesiredSize.BlockStartAscent()); } // Subtract border/padding in vertical axis, to get _just_ // the effective computed value of the "height" property. nscoord childDesiredHeight = childDesiredSize.Height() - childRSForMeasuringHeight.ComputedPhysicalBorderPadding().TopBottom(); @@ -1560,25 +1560,37 @@ FlexItem::FlexItem(nsHTMLReflowState& aF mShareOfWeightSoFar(0.0f), mIsFrozen(false), mHadMinViolation(false), mHadMaxViolation(false), mHadMeasuringReflow(false), mIsStretched(false), mIsStrut(false), // mNeedsMinSizeAutoResolution is initialized in CheckForMinSizeAuto() - mWM(aFlexItemReflowState.GetWritingMode()), - mAlignSelf(aFlexItemReflowState.mStylePosition->mAlignSelf) + mWM(aFlexItemReflowState.GetWritingMode()) + // mAlignSelf, see below { MOZ_ASSERT(mFrame, "expecting a non-null child frame"); MOZ_ASSERT(mFrame->GetType() != nsGkAtoms::placeholderFrame, "placeholder frames should not be treated as flex items"); MOZ_ASSERT(!(mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW), "out-of-flow frames should not be treated as flex items"); + mAlignSelf = aFlexItemReflowState.mStylePosition->ComputedAlignSelf( + aFlexItemReflowState.mStyleDisplay, + mFrame->StyleContext()->GetParent()); + if (MOZ_UNLIKELY(mAlignSelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + mAlignSelf = NS_STYLE_ALIGN_STRETCH; + } + + // XXX strip off the <overflow-position> bit until we implement that + mAlignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + SetFlexBaseSizeAndMainSize(aFlexBaseSize); CheckForMinSizeAuto(aFlexItemReflowState, aAxisTracker); // Assert that any "auto" margin components are set to 0. // (We'll resolve them later; until then, we want to treat them as 0-sized.) #ifdef DEBUG { const nsStyleSides& styleMargin = @@ -1587,35 +1599,29 @@ FlexItem::FlexItem(nsHTMLReflowState& aF if (styleMargin.GetUnit(side) == eStyleUnit_Auto) { MOZ_ASSERT(GetMarginComponentForSide(side) == 0, "Someone else tried to resolve our auto margin"); } } } #endif // DEBUG - // Resolve "align-self: auto" to parent's "align-items" value. - if (mAlignSelf == NS_STYLE_ALIGN_SELF_AUTO) { - mAlignSelf = - mFrame->StyleContext()->GetParent()->StylePosition()->mAlignItems; - } - // If the flex item's inline axis is the same as the cross axis, then // 'align-self:baseline' is identical to 'flex-start'. If that's the case, we // just directly convert our align-self value here, so that we don't have to // handle this with special cases elsewhere. // Moreover: for the time being (until we support writing-modes), // all inline axes are horizontal -- so we can just check if the cross axis // is horizontal. // FIXME: Once we support writing-mode (vertical text), this // IsCrossAxisHorizontal check won't be sufficient anymore -- we'll actually // need to compare our inline axis vs. the cross axis. - if (mAlignSelf == NS_STYLE_ALIGN_ITEMS_BASELINE && + if (mAlignSelf == NS_STYLE_ALIGN_BASELINE && aAxisTracker.IsCrossAxisHorizontal()) { - mAlignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + mAlignSelf = NS_STYLE_ALIGN_FLEX_START; } } // Simplified constructor for creating a special "strut" FlexItem, for a child // with visibility:collapse. The strut has 0 main-size, and it only exists to // impose a minimum cross size on whichever FlexLine it ends up in. FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM) @@ -1638,17 +1644,17 @@ FlexItem::FlexItem(nsIFrame* aChildFrame mIsFrozen(true), mHadMinViolation(false), mHadMaxViolation(false), mHadMeasuringReflow(false), mIsStretched(false), mIsStrut(true), // (this is the constructor for making struts, after all) mNeedsMinSizeAutoResolution(false), mWM(aContainerWM), - mAlignSelf(NS_STYLE_ALIGN_ITEMS_FLEX_START) + mAlignSelf(NS_STYLE_ALIGN_FLEX_START) { MOZ_ASSERT(mFrame, "expecting a non-null child frame"); MOZ_ASSERT(NS_STYLE_VISIBILITY_COLLAPSE == mFrame->StyleVisibility()->mVisible, "Should only make struts for children with 'visibility:collapse'"); MOZ_ASSERT(mFrame->GetType() != nsGkAtoms::placeholderFrame, "placeholder frames should not be treated as flex items"); MOZ_ASSERT(!(mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW), @@ -1828,16 +1834,17 @@ public: // If aItem has any 'auto' margins in the main axis, this method updates the // corresponding values in its margin. void ResolveAutoMarginsInMainAxis(FlexItem& aItem); private: nscoord mPackingSpaceRemaining; uint32_t mNumAutoMarginsInMainAxis; uint32_t mNumPackingSpacesRemaining; + // XXX this should be uint16_t when we add explicit fallback handling uint8_t mJustifyContent; }; // Utility class for managing our position along the cross axis along // the whole flex container (at a higher level than a single line). // The "0" position represents the cross-start edge of the flex container's // content-box. class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker { @@ -1861,16 +1868,17 @@ private: // deals with FlexLines, not with individual FlexItems or frames.) void EnterMargin(const nsMargin& aMargin) = delete; void ExitMargin(const nsMargin& aMargin) = delete; void EnterChildFrame(nscoord aChildFrameSize) = delete; void ExitChildFrame(nscoord aChildFrameSize) = delete; nscoord mPackingSpaceRemaining; uint32_t mNumPackingSpacesRemaining; + // XXX this should be uint16_t when we add explicit fallback handling uint8_t mAlignContent; }; // Utility class for managing our position along the cross axis, *within* a // single flex line. class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker : public PositionTracker { public: explicit SingleLineCrossAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker); @@ -2423,16 +2431,24 @@ MainAxisPositionTracker:: nscoord aContentBoxMainSize) : PositionTracker(aAxisTracker.GetMainAxis(), aAxisTracker.IsMainAxisReversed()), mPackingSpaceRemaining(aContentBoxMainSize), // we chip away at this below mNumAutoMarginsInMainAxis(0), mNumPackingSpacesRemaining(0), mJustifyContent(aJustifyContent) { + // 'auto' behaves as 'stretch' which behaves as 'flex-start' in the main axis + if (mJustifyContent == NS_STYLE_JUSTIFY_AUTO) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; + } + + // XXX strip off the <overflow-position> bit until we implement that + mJustifyContent &= ~NS_STYLE_JUSTIFY_FLAG_BITS; + // mPackingSpaceRemaining is initialized to the container's main size. Now // we'll subtract out the main sizes of our flex items, so that it ends up // with the *actual* amount of packing space. for (const FlexItem* item = aLine->GetFirstItem(); item; item = item->getNext()) { mPackingSpaceRemaining -= item->GetOuterMainSize(mAxis); mNumAutoMarginsInMainAxis += item->GetNumAutoMarginsInAxis(mAxis); } @@ -2441,58 +2457,71 @@ MainAxisPositionTracker:: // No available packing space to use for resolving auto margins. mNumAutoMarginsInMainAxis = 0; } // If packing space is negative, 'space-between' behaves like 'flex-start', // and 'space-around' behaves like 'center'. In those cases, it's simplest to // just pretend we have a different 'justify-content' value and share code. if (mPackingSpaceRemaining < 0) { - if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; - } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_CENTER; + if (mJustifyContent == NS_STYLE_JUSTIFY_SPACE_BETWEEN) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_SPACE_AROUND) { + mJustifyContent = NS_STYLE_JUSTIFY_CENTER; } } + // Map 'start'/'end' to 'flex-start'/'flex-end'. + if (mJustifyContent == NS_STYLE_JUSTIFY_START) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_END) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_END; + } + // If our main axis is (internally) reversed, swap the justify-content // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { - if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_START) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_END; - } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_END) { - mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; + if (mJustifyContent == NS_STYLE_JUSTIFY_FLEX_START) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_END; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_FLEX_END) { + mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START; } } // Figure out how much space we'll set aside for auto margins or // packing spaces, and advance past any leading packing-space. if (mNumAutoMarginsInMainAxis == 0 && mPackingSpaceRemaining != 0 && !aLine->IsEmpty()) { switch (mJustifyContent) { - case NS_STYLE_JUSTIFY_CONTENT_FLEX_START: + case NS_STYLE_JUSTIFY_LEFT: + case NS_STYLE_JUSTIFY_RIGHT: + case NS_STYLE_JUSTIFY_BASELINE: + case NS_STYLE_JUSTIFY_LAST_BASELINE: + case NS_STYLE_JUSTIFY_SPACE_EVENLY: + NS_WARNING("NYI: justify-content:left/right/baseline/last-baseline/space-evenly"); + case NS_STYLE_JUSTIFY_FLEX_START: // All packing space should go at the end --> nothing to do here. break; - case NS_STYLE_JUSTIFY_CONTENT_FLEX_END: + case NS_STYLE_JUSTIFY_FLEX_END: // All packing space goes at the beginning mPosition += mPackingSpaceRemaining; break; - case NS_STYLE_JUSTIFY_CONTENT_CENTER: + case NS_STYLE_JUSTIFY_CENTER: // Half the packing space goes at the beginning mPosition += mPackingSpaceRemaining / 2; break; - case NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN: + case NS_STYLE_JUSTIFY_SPACE_BETWEEN: MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'flex-start' " "instead of 'space-between'"); // 1 packing space between each flex item, no packing space at ends. mNumPackingSpacesRemaining = aLine->NumItems() - 1; break; - case NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND: + case NS_STYLE_JUSTIFY_SPACE_AROUND: MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'center' " "instead of 'space-around'"); // 1 packing space between each flex item, plus half a packing space // at beginning & end. So our number of full packing-spaces is equal // to the number of flex items. mNumPackingSpacesRemaining = aLine->NumItems(); if (mNumPackingSpacesRemaining > 0) { @@ -2543,18 +2572,18 @@ MainAxisPositionTracker::ResolveAutoMarg } } } void MainAxisPositionTracker::TraversePackingSpace() { if (mNumPackingSpacesRemaining) { - MOZ_ASSERT(mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN || - mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND, + MOZ_ASSERT(mJustifyContent == NS_STYLE_JUSTIFY_SPACE_BETWEEN || + mJustifyContent == NS_STYLE_JUSTIFY_SPACE_AROUND, "mNumPackingSpacesRemaining only applies for " "space-between/space-around"); MOZ_ASSERT(mPackingSpaceRemaining >= 0, "ran out of packing space earlier than we expected"); // NOTE: This integer math will skew the distribution of remainder // app-units towards the end, which is fine. @@ -2576,16 +2605,24 @@ CrossAxisPositionTracker:: : PositionTracker(aAxisTracker.GetCrossAxis(), aAxisTracker.IsCrossAxisReversed()), mPackingSpaceRemaining(0), mNumPackingSpacesRemaining(0), mAlignContent(aAlignContent) { MOZ_ASSERT(aFirstLine, "null first line pointer"); + // 'auto' behaves as 'stretch' + if (mAlignContent == NS_STYLE_ALIGN_AUTO) { + mAlignContent = NS_STYLE_ALIGN_STRETCH; + } + + // XXX strip of the <overflow-position> bit until we implement that + mAlignContent &= ~NS_STYLE_ALIGN_FLAG_BITS; + if (aIsCrossSizeDefinite && !aFirstLine->getNext()) { // "If the flex container has only a single line (even if it's a // multi-line flex container) and has a definite cross size, the cross // size of the flex line is the flex container's inner cross size." // SOURCE: http://dev.w3.org/csswg/css-flexbox/#algo-line-break // NOTE: This means (by definition) that there's no packing space, which // means we don't need to be concerned with "align-conent" at all and we // can return early. This is handy, because this is the usual case (for @@ -2609,57 +2646,72 @@ CrossAxisPositionTracker:: numLines++; } // If packing space is negative, 'space-between' and 'stretch' behave like // 'flex-start', and 'space-around' behaves like 'center'. In those cases, // it's simplest to just pretend we have a different 'align-content' value // and share code. if (mPackingSpaceRemaining < 0) { - if (mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN || - mAlignContent == NS_STYLE_ALIGN_CONTENT_STRETCH) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START; - } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_AROUND) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_CENTER; + if (mAlignContent == NS_STYLE_ALIGN_SPACE_BETWEEN || + mAlignContent == NS_STYLE_ALIGN_STRETCH) { + mAlignContent = NS_STYLE_ALIGN_FLEX_START; + } else if (mAlignContent == NS_STYLE_ALIGN_SPACE_AROUND) { + mAlignContent = NS_STYLE_ALIGN_CENTER; } } + // Map 'start'/'end' to 'flex-start'/'flex-end'. + if (mAlignContent == NS_STYLE_ALIGN_START) { + mAlignContent = NS_STYLE_ALIGN_FLEX_START; + } else if (mAlignContent == NS_STYLE_ALIGN_END) { + mAlignContent = NS_STYLE_ALIGN_FLEX_END; + } + // If our cross axis is (internally) reversed, swap the align-content // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { - if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_START) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_END; - } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_END) { - mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START; + if (mAlignContent == NS_STYLE_ALIGN_FLEX_START) { + mAlignContent = NS_STYLE_ALIGN_FLEX_END; + } else if (mAlignContent == NS_STYLE_ALIGN_FLEX_END) { + mAlignContent = NS_STYLE_ALIGN_FLEX_START; } } // Figure out how much space we'll set aside for packing spaces, and advance // past any leading packing-space. if (mPackingSpaceRemaining != 0) { switch (mAlignContent) { - case NS_STYLE_ALIGN_CONTENT_FLEX_START: + case NS_STYLE_JUSTIFY_LEFT: + case NS_STYLE_JUSTIFY_RIGHT: + case NS_STYLE_ALIGN_SELF_START: + case NS_STYLE_ALIGN_SELF_END: + case NS_STYLE_ALIGN_SPACE_EVENLY: + case NS_STYLE_ALIGN_BASELINE: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: align-self:left/right/self-start/self-end/space-evenly/baseline/last-baseline"); + case NS_STYLE_ALIGN_FLEX_START: // All packing space should go at the end --> nothing to do here. break; - case NS_STYLE_ALIGN_CONTENT_FLEX_END: + case NS_STYLE_ALIGN_FLEX_END: // All packing space goes at the beginning mPosition += mPackingSpaceRemaining; break; - case NS_STYLE_ALIGN_CONTENT_CENTER: + case NS_STYLE_ALIGN_CENTER: // Half the packing space goes at the beginning mPosition += mPackingSpaceRemaining / 2; break; - case NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN: + case NS_STYLE_ALIGN_SPACE_BETWEEN: MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'flex-start' " "instead of 'space-between'"); // 1 packing space between each flex line, no packing space at ends. mNumPackingSpacesRemaining = numLines - 1; break; - case NS_STYLE_ALIGN_CONTENT_SPACE_AROUND: { + case NS_STYLE_ALIGN_SPACE_AROUND: { MOZ_ASSERT(mPackingSpaceRemaining >= 0, "negative packing space should make us use 'center' " "instead of 'space-around'"); // 1 packing space between each flex line, plus half a packing space // at beginning & end. So our number of full packing-spaces is equal // to the number of flex lines. mNumPackingSpacesRemaining = numLines; // The edges (start/end) share one full packing space @@ -2669,17 +2721,17 @@ CrossAxisPositionTracker:: // ...and we'll use half of that right now, at the start mPosition += totalEdgePackingSpace / 2; // ...but we need to subtract all of it right away, so that we won't // hand out any of it to intermediate packing spaces. mPackingSpaceRemaining -= totalEdgePackingSpace; mNumPackingSpacesRemaining--; break; } - case NS_STYLE_ALIGN_CONTENT_STRETCH: { + case NS_STYLE_ALIGN_STRETCH: { // Split space equally between the lines: MOZ_ASSERT(mPackingSpaceRemaining > 0, "negative packing space should make us use 'flex-start' " "instead of 'stretch' (and we shouldn't bother with this " "code if we have 0 packing space)"); uint32_t numLinesLeft = numLines; for (FlexLine* line = aFirstLine; line; line = line->getNext()) { @@ -2701,18 +2753,18 @@ CrossAxisPositionTracker:: } } } void CrossAxisPositionTracker::TraversePackingSpace() { if (mNumPackingSpacesRemaining) { - MOZ_ASSERT(mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN || - mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_AROUND, + MOZ_ASSERT(mAlignContent == NS_STYLE_ALIGN_SPACE_BETWEEN || + mAlignContent == NS_STYLE_ALIGN_SPACE_AROUND, "mNumPackingSpacesRemaining only applies for " "space-between/space-around"); MOZ_ASSERT(mPackingSpaceRemaining >= 0, "ran out of packing space earlier than we expected"); // NOTE: This integer math will skew the distribution of remainder // app-units towards the end, which is fine. @@ -2737,17 +2789,17 @@ FlexLine::ComputeCrossSizeAndBaseline(co { nscoord crossStartToFurthestBaseline = nscoord_MIN; nscoord crossEndToFurthestBaseline = nscoord_MIN; nscoord largestOuterCrossSize = 0; for (const FlexItem* item = mItems.getFirst(); item; item = item->getNext()) { nscoord curOuterCrossSize = item->GetOuterCrossSize(aAxisTracker.GetCrossAxis()); - if (item->GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE && + if (item->GetAlignSelf() == NS_STYLE_ALIGN_BASELINE && item->GetNumAutoMarginsInAxis(aAxisTracker.GetCrossAxis()) == 0) { // FIXME: Once we support "writing-mode", we'll have to do baseline // alignment in vertical flex containers here (w/ horizontal cross-axes). // Find distance from our item's cross-start and cross-end margin-box // edges to its baseline. // // Here's a diagram of a flex-item that we might be doing this on. @@ -2810,17 +2862,17 @@ FlexLine::ComputeCrossSizeAndBaseline(co void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize, const FlexboxAxisTracker& aAxisTracker) { AxisOrientationType crossAxis = aAxisTracker.GetCrossAxis(); // We stretch IFF we are align-self:stretch, have no auto margins in // cross axis, and have cross-axis size property == "auto". If any of those // conditions don't hold up, we won't stretch. - if (mAlignSelf != NS_STYLE_ALIGN_ITEMS_STRETCH || + if (mAlignSelf != NS_STYLE_ALIGN_STRETCH || GetNumAutoMarginsInAxis(crossAxis) != 0 || eStyleUnit_Auto != aAxisTracker.ComputedCrossSize(mFrame).GetUnit()) { return; } // If we've already been stretched, we can bail out early, too. // No need to redo the calculation. if (mIsStretched) { @@ -2889,43 +2941,56 @@ SingleLineCrossAxisPositionTracker:: // in the cross axis. if (aItem.GetNumAutoMarginsInAxis(mAxis)) { return; } uint8_t alignSelf = aItem.GetAlignSelf(); // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any // auto-sized items (which we've already done). - if (alignSelf == NS_STYLE_ALIGN_ITEMS_STRETCH) { - alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + if (alignSelf == NS_STYLE_ALIGN_STRETCH) { + alignSelf = NS_STYLE_ALIGN_FLEX_START; + } + + // Map 'start'/'end' to 'flex-start'/'flex-end'. + if (alignSelf == NS_STYLE_ALIGN_START) { + alignSelf = NS_STYLE_ALIGN_FLEX_START; + } else if (alignSelf == NS_STYLE_ALIGN_END) { + alignSelf = NS_STYLE_ALIGN_FLEX_END; } // If our cross axis is (internally) reversed, swap the align-self // "flex-start" and "flex-end" behaviors: if (aAxisTracker.AreAxesInternallyReversed()) { - if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_START) { - alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_END; - } else if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_END) { - alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + if (alignSelf == NS_STYLE_ALIGN_FLEX_START) { + alignSelf = NS_STYLE_ALIGN_FLEX_END; + } else if (alignSelf == NS_STYLE_ALIGN_FLEX_END) { + alignSelf = NS_STYLE_ALIGN_FLEX_START; } } switch (alignSelf) { - case NS_STYLE_ALIGN_ITEMS_FLEX_START: + case NS_STYLE_JUSTIFY_LEFT: + case NS_STYLE_JUSTIFY_RIGHT: + case NS_STYLE_ALIGN_SELF_START: + case NS_STYLE_ALIGN_SELF_END: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: align-self:left/right/self-start/self-end/last-baseline"); + case NS_STYLE_ALIGN_FLEX_START: // No space to skip over -- we're done. break; - case NS_STYLE_ALIGN_ITEMS_FLEX_END: + case NS_STYLE_ALIGN_FLEX_END: mPosition += aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis); break; - case NS_STYLE_ALIGN_ITEMS_CENTER: + case NS_STYLE_ALIGN_CENTER: // Note: If cross-size is odd, the "after" space will get the extra unit. mPosition += (aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis)) / 2; break; - case NS_STYLE_ALIGN_ITEMS_BASELINE: { + case NS_STYLE_ALIGN_BASELINE: { // Normally, baseline-aligned items are collectively aligned with the // line's cross-start edge; however, if our cross axis is (internally) // reversed, we instead align them with the cross-end edge. AxisEdgeType baselineAlignEdge = aAxisTracker.AreAxesInternallyReversed() ? eAxisEdge_End : eAxisEdge_Start; nscoord itemBaselineOffset = @@ -3439,17 +3504,17 @@ nsFlexContainerFrame::SizeItemInCrossAxi // for our cross size [width].) aItem.SetCrossSize(aChildReflowState.ComputedWidth()); return; } MOZ_ASSERT(!aItem.HadMeasuringReflow(), "We shouldn't need more than one measuring reflow"); - if (aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_STRETCH) { + if (aItem.GetAlignSelf() == NS_STYLE_ALIGN_STRETCH) { // This item's got "align-self: stretch", so we probably imposed a // stretched computed height on it during its previous reflow. We're // not imposing that height for *this* measuring reflow, so we need to // tell it to treat this reflow as a vertical resize (regardless of // whether any of its ancestors are being resized). aChildReflowState.SetVResize(true); } nsHTMLReflowMetrics childDesiredSize(aChildReflowState); @@ -3496,17 +3561,17 @@ nsFlexContainerFrame::SizeItemInCrossAxi // (normal case) aItem.SetCrossSize(childDesiredSize.Height() - crossAxisBorderPadding); } // If this is the first child, save its ascent, since it may be what // establishes the container's baseline. Also save the ascent if this child // needs to be baseline-aligned. (Else, we don't care about baseline/ascent.) if (aItem.Frame() == mFrames.FirstChild() || - aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE) { + aItem.GetAlignSelf() == NS_STYLE_ALIGN_BASELINE) { aItem.SetAscent(childDesiredSize.BlockStartAscent()); } } void FlexLine::PositionItemsInCrossAxis(nscoord aLineStartPosition, const FlexboxAxisTracker& aAxisTracker) { @@ -3731,17 +3796,18 @@ nsFlexContainerFrame::DoFlexLayout(nsPre const nscoord contentBoxCrossSize = ComputeCrossSize(aReflowState, aAxisTracker, sumLineCrossSizes, aAvailableBSizeForContent, &isCrossSizeDefinite, aStatus); // Set up state for cross-axis alignment, at a high level (outside the // scope of a particular flex line) CrossAxisPositionTracker crossAxisPosnTracker(lines.getFirst(), - aReflowState.mStylePosition->mAlignContent, + aReflowState.mStylePosition->ComputedAlignContent( + aReflowState.mStyleDisplay), contentBoxCrossSize, isCrossSizeDefinite, aAxisTracker); // Now that we know the cross size of each line (including // "align-content:stretch" adjustments, from the CrossAxisPositionTracker // constructor), we can create struts for any flex items with // "visibility: collapse" (and restart flex layout). if (aStruts.IsEmpty()) { // (Don't make struts if we already did) @@ -3770,17 +3836,19 @@ nsFlexContainerFrame::DoFlexLayout(nsPre contentBoxCrossSize, aReflowState, aAxisTracker); } } for (FlexLine* line = lines.getFirst(); line; line = line->getNext()) { // Main-Axis Alignment - Flexbox spec section 9.5 // ============================================== - line->PositionItemsInMainAxis(aReflowState.mStylePosition->mJustifyContent, + auto justifyContent = + aReflowState.mStylePosition->ComputedJustifyContent(aReflowState.mStyleDisplay); + line->PositionItemsInMainAxis(justifyContent, aContentBoxMainSize, aAxisTracker); // Cross-Axis Alignment - Flexbox spec section 9.6 // =============================================== line->PositionItemsInCrossAxis(crossAxisPosnTracker.GetPosition(), aAxisTracker); crossAxisPosnTracker.TraverseLine(*line); @@ -4010,17 +4078,17 @@ nsFlexContainerFrame::ReflowFlexItem(nsP didOverrideComputedWidth = true; } else { childReflowState.SetComputedHeight(aItem.GetMainSize()); didOverrideComputedHeight = true; } // Override reflow state's computed cross-size, for stretched items. if (aItem.IsStretched()) { - MOZ_ASSERT(aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_STRETCH, + MOZ_ASSERT(aItem.GetAlignSelf() == NS_STYLE_ALIGN_STRETCH, "stretched item w/o 'align-self: stretch'?"); if (aAxisTracker.IsCrossAxisHorizontal()) { childReflowState.SetComputedWidth(aItem.GetCrossSize()); didOverrideComputedWidth = true; } else { // If this item's height is stretched, it's a relative height. aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); childReflowState.SetComputedHeight(aItem.GetCrossSize());
--- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4295,16 +4295,19 @@ nsFrame::ComputeSize(nsRenderingContext WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, const LogicalSize& aMargin, const LogicalSize& aBorder, const LogicalSize& aPadding, ComputeSizeFlags aFlags) { + MOZ_ASSERT(GetIntrinsicRatio() == nsSize(0,0), + "Please override this method and call " + "nsLayoutUtils::ComputeSizeWithIntrinsicDimensions instead."); LogicalSize result = ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin, aBorder, aPadding, aFlags & ComputeSizeFlags::eShrinkWrap); LogicalSize boxSizingAdjust(aWM); const nsStylePosition *stylePos = StylePosition(); switch (stylePos->mBoxSizing) { @@ -4316,19 +4319,22 @@ nsFrame::ComputeSize(nsRenderingContext } nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) + aBorder.ISize(aWM) + aPadding.ISize(aWM) - boxSizingAdjust.ISize(aWM); const nsStyleCoord* inlineStyleCoord = &stylePos->ISize(aWM); const nsStyleCoord* blockStyleCoord = &stylePos->BSize(aWM); - bool isFlexItem = IsFlexItem(); + nsIAtom* parentFrameType = GetParent() ? GetParent()->GetType() : nullptr; + bool isGridItem = (parentFrameType == nsGkAtoms::gridContainerFrame && + !(GetStateBits() & NS_FRAME_OUT_OF_FLOW)); + bool isFlexItem = (parentFrameType == nsGkAtoms::flexContainerFrame && + !(GetStateBits() & NS_FRAME_OUT_OF_FLOW)); bool isInlineFlexItem = false; - if (isFlexItem) { // Flex items use their "flex-basis" property in place of their main-size // property (e.g. "width") for sizing purposes, *unless* they have // "flex-basis:auto", in which case they use their main-size property after // all. uint32_t flexDirection = GetParent()->StylePosition()->mFlexDirection; isInlineFlexItem = flexDirection == NS_STYLE_FLEX_DIRECTION_ROW || @@ -4360,39 +4366,45 @@ nsFrame::ComputeSize(nsRenderingContext if (inlineStyleCoord->GetUnit() != eStyleUnit_Auto) { result.ISize(aWM) = nsLayoutUtils::ComputeISizeValue(aRenderingContext, this, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize, *inlineStyleCoord); } - const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM); - // Flex items ignore their min & max sizing properties in their // flex container's main-axis. (Those properties get applied later in // the flexbox algorithm.) + const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM); + nscoord maxISize = NS_UNCONSTRAINEDSIZE; if (maxISizeCoord.GetUnit() != eStyleUnit_None && !(isFlexItem && isInlineFlexItem)) { - nscoord maxISize = + maxISize = nsLayoutUtils::ComputeISizeValue(aRenderingContext, this, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize, maxISizeCoord); result.ISize(aWM) = std::min(maxISize, result.ISize(aWM)); } const nsStyleCoord& minISizeCoord = stylePos->MinISize(aWM); - nscoord minISize; if (minISizeCoord.GetUnit() != eStyleUnit_Auto && !(isFlexItem && isInlineFlexItem)) { minISize = nsLayoutUtils::ComputeISizeValue(aRenderingContext, this, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize, minISizeCoord); + } else if (MOZ_UNLIKELY(isGridItem)) { + // This implements "Implied Minimum Size of Grid Items". + // https://drafts.csswg.org/css-grid/#min-size-auto + minISize = std::min(maxISize, GetMinISize(aRenderingContext)); + if (inlineStyleCoord->IsCoordPercentCalcUnit()) { + minISize = std::min(minISize, result.ISize(aWM)); + } } else { // Treat "min-width: auto" as 0. // NOTE: Technically, "auto" is supposed to behave like "min-content" on // flex items. However, we don't need to worry about that here, because // flex items' min-sizes are intentionally ignored until the flex // container explicitly considers them during space distribution. minISize = 0; }
--- a/layout/generic/nsFrameSetFrame.cpp +++ b/layout/generic/nsFrameSetFrame.cpp @@ -425,22 +425,22 @@ void nsHTMLFramesetFrame::CalculateRowCo const nsFramesetSpec* aSpecs, nscoord* aValues) { // aNumSpecs maximum value is NS_MAX_FRAMESET_SPEC_COUNT PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(int32_t)); int32_t fixedTotal = 0; int32_t numFixed = 0; - nsAutoArrayPtr<int32_t> fixed(new int32_t[aNumSpecs]); + auto fixed = MakeUnique<int32_t[]>(aNumSpecs); int32_t numPercent = 0; - nsAutoArrayPtr<int32_t> percent(new int32_t[aNumSpecs]); + auto percent = MakeUnique<int32_t[]>(aNumSpecs); int32_t relativeSums = 0; int32_t numRelative = 0; - nsAutoArrayPtr<int32_t> relative(new int32_t[aNumSpecs]); + auto relative = MakeUnique<int32_t[]>(aNumSpecs); if (MOZ_UNLIKELY(!fixed || !percent || !relative)) { return; // NS_ERROR_OUT_OF_MEMORY } int32_t i, j; // initialize the fixed, percent, relative indices, allocate the fixed sizes and zero the others @@ -462,47 +462,47 @@ void nsHTMLFramesetFrame::CalculateRowCo numRelative++; relativeSums += aSpecs[i].mValue; break; } } // scale the fixed sizes if they total too much (or too little and there aren't any percent or relative) if ((fixedTotal > aSize) || ((fixedTotal < aSize) && (0 == numPercent) && (0 == numRelative))) { - Scale(aSize, numFixed, fixed, aNumSpecs, aValues); + Scale(aSize, numFixed, fixed.get(), aNumSpecs, aValues); return; } int32_t percentMax = aSize - fixedTotal; int32_t percentTotal = 0; // allocate the percentage sizes from what is left over from the fixed allocation for (i = 0; i < numPercent; i++) { j = percent[i]; aValues[j] = NSToCoordRound((float)aSpecs[j].mValue * (float)aSize / 100.0f); percentTotal += aValues[j]; } // scale the percent sizes if they total too much (or too little and there aren't any relative) if ((percentTotal > percentMax) || ((percentTotal < percentMax) && (0 == numRelative))) { - Scale(percentMax, numPercent, percent, aNumSpecs, aValues); + Scale(percentMax, numPercent, percent.get(), aNumSpecs, aValues); return; } int32_t relativeMax = percentMax - percentTotal; int32_t relativeTotal = 0; // allocate the relative sizes from what is left over from the percent allocation for (i = 0; i < numRelative; i++) { j = relative[i]; aValues[j] = NSToCoordRound((float)aSpecs[j].mValue * (float)relativeMax / (float)relativeSums); relativeTotal += aValues[j]; } // scale the relative sizes if they take up too much or too little if (relativeTotal != relativeMax) { - Scale(relativeMax, numRelative, relative, aNumSpecs, aValues); + Scale(relativeMax, numRelative, relative.get(), aNumSpecs, aValues); } } /** * Translate the rows/cols integer sizes into an array of specs for * each cell in the frameset. Reverse of CalculateRowCol() behaviour. * This allows us to maintain the user size info through reflows. @@ -847,38 +847,38 @@ nsHTMLFramesetFrame::Reflow(nsPresContex mDrag.UnSet(); NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); return; } CalculateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get()); CalculateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get()); - nsAutoArrayPtr<bool> verBordersVis; // vertical borders visibility - nsAutoArrayPtr<nscolor> verBorderColors; - nsAutoArrayPtr<bool> horBordersVis; // horizontal borders visibility - nsAutoArrayPtr<nscolor> horBorderColors; + UniquePtr<bool[]> verBordersVis; // vertical borders visibility + UniquePtr<nscolor[]> verBorderColors; + UniquePtr<bool[]> horBordersVis; // horizontal borders visibility + UniquePtr<nscolor[]> horBorderColors; nscolor borderColor = GetBorderColor(); nsFrameborder frameborder = GetFrameBorder(); if (firstTime) { // Check for overflow in memory allocations using mNumCols and mNumRows // which have a maxium value of NS_MAX_FRAMESET_SPEC_COUNT. PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(bool)); PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscolor)); - verBordersVis = new bool[mNumCols]; - verBorderColors = new nscolor[mNumCols]; + verBordersVis = MakeUnique<bool[]>(mNumCols); + verBorderColors = MakeUnique<nscolor[]>(mNumCols); for (int verX = 0; verX < mNumCols; verX++) { verBordersVis[verX] = false; verBorderColors[verX] = NO_COLOR; } - horBordersVis = new bool[mNumRows]; - horBorderColors = new nscolor[mNumRows]; + horBordersVis = MakeUnique<bool[]>(mNumRows); + horBorderColors = MakeUnique<nscolor[]>(mNumRows); for (int horX = 0; horX < mNumRows; horX++) { horBordersVis[horX] = false; horBorderColors[horX] = NO_COLOR; } } // reflow the children int32_t lastRow = 0;
--- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -582,16 +582,24 @@ struct MOZ_STACK_CLASS nsGridContainerFr */ void CalculateSizes(GridReflowState& aState, nsTArray<GridItemInfo>& aGridItems, const TrackSizingFunctions& aFunctions, nscoord aContentSize, LineRange GridArea::* aRange, IntrinsicISizeType aConstraint); + /** + * Apply 'align/justify-content', whichever is relevant for this axis. + * https://drafts.csswg.org/css-align-3/#propdef-align-content + */ + void AlignJustifyContent(const nsHTMLReflowState& aReflowState, + const LogicalSize& aContainerSize); + + #ifdef DEBUG void Dump() const { for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { printf(" %d: ", i); mSizes[i].Dump(); printf("\n"); } @@ -795,23 +803,28 @@ static bool IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex) { return IsNameWithSuffix(aString, NS_LITERAL_STRING("-start"), aIndex); } static nscoord GridLinePosition(uint32_t aLine, const nsTArray<TrackSize>& aTrackSizes) { - const uint32_t endIndex = aLine; - MOZ_ASSERT(endIndex <= aTrackSizes.Length(), "aTrackSizes is too small"); - nscoord pos = 0; - for (uint32_t i = 0; i < endIndex; ++i) { - pos += aTrackSizes[i].mBase; + if (aTrackSizes.Length() == 0) { + // https://drafts.csswg.org/css-grid/#grid-definition + // "... the explicit grid still contains one grid line in each axis." + MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid"); + return nscoord(0); } - return pos; + MOZ_ASSERT(aLine <= aTrackSizes.Length(), "aTrackSizes is too small"); + if (aLine == aTrackSizes.Length()) { + const TrackSize& sz = aTrackSizes[aLine - 1]; + return sz.mPosition + sz.mBase; + } + return aTrackSizes[aLine].mPosition; } /** * (XXX share this utility function with nsFlexContainerFrame at some point) * * Helper for BuildDisplayList, to implement this special-case for grid * items from the spec: * The painting order of grid items is exactly the same as inline blocks, @@ -824,16 +837,298 @@ GetDisplayFlagsForGridItem(nsIFrame* aFr { const nsStylePosition* pos = aFrame->StylePosition(); if (pos->mZIndex.GetUnit() == eStyleUnit_Integer) { return nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT; } return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT; } +static nscoord +SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin, + LogicalAxis aAxis, nscoord aCBSize) +{ + nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM) + : aSize.ISize(aWM); + return aCBSize - (size + aMargin); +} + +static bool +AlignJustifySelf(uint8_t aAlignment, bool aOverflowSafe, LogicalAxis aAxis, + bool aSameSide, nscoord aCBSize, const nsHTMLReflowState& aRS, + const LogicalSize& aChildSize, LogicalSize* aContentSize, + LogicalPoint* aPos) +{ + MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' " + "computed value for normal flow grid item"); + MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_LEFT && + aAlignment != NS_STYLE_ALIGN_RIGHT, + "caller should map that to the corresponding START/END"); + + // Map some alignment values to 'start' / 'end'. + switch (aAlignment) { + case NS_STYLE_ALIGN_SELF_START: // align/justify-self: self-start + aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_START + : NS_STYLE_ALIGN_END; + break; + case NS_STYLE_ALIGN_SELF_END: // align/justify-self: self-end + aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_END + : NS_STYLE_ALIGN_START; + break; + case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid + aAlignment = NS_STYLE_ALIGN_START; + break; + case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid + aAlignment = NS_STYLE_ALIGN_END; + break; + } + + // XXX try to condense this code a bit by adding the necessary convenience + // methods? (bug 1209710) + + // Get the item's margin corresponding to the container's start/end side. + const LogicalMargin margin = aRS.ComputedLogicalMargin(); + WritingMode wm = aRS.GetWritingMode(); + nscoord marginStart, marginEnd; + if (aAxis == eLogicalAxisBlock) { + if (MOZ_LIKELY(aSameSide)) { + marginStart = margin.BStart(wm); + marginEnd = margin.BEnd(wm); + } else { + marginStart = margin.BEnd(wm); + marginEnd = margin.BStart(wm); + } + } else { + if (MOZ_LIKELY(aSameSide)) { + marginStart = margin.IStart(wm); + marginEnd = margin.IEnd(wm); + } else { + marginStart = margin.IEnd(wm); + marginEnd = margin.IStart(wm); + } + } + + // https://drafts.csswg.org/css-align-3/#overflow-values + // This implements <overflow-position> = 'safe'. + if (MOZ_UNLIKELY(aOverflowSafe) && aAlignment != NS_STYLE_ALIGN_START) { + nscoord space = SpaceToFill(wm, aChildSize, marginStart + marginEnd, + aAxis, aCBSize); + // XXX we might want to include == 0 here as an optimization - + // I need to see what the baseline/last-baseline code looks like first. + if (space < 0) { + aAlignment = NS_STYLE_ALIGN_START; + } + } + + // Set the position and size (aPos/aContentSize) for the requested alignment. + bool didResize = false; + nscoord offset = 0; + switch (aAlignment) { + case NS_STYLE_ALIGN_BASELINE: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: baseline/last-baseline for grid (bug 1151204)"); // XXX + case NS_STYLE_ALIGN_START: + offset = marginStart; + break; + case NS_STYLE_ALIGN_END: { + nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm) + : aChildSize.ISize(wm); + offset = aCBSize - (size + marginEnd); + break; + } + case NS_STYLE_ALIGN_CENTER: + offset = SpaceToFill(wm, aChildSize, marginStart + marginEnd, + aAxis, aCBSize) / 2; + break; + case NS_STYLE_ALIGN_STRETCH: { + offset = marginStart; + const auto& styleMargin = aRS.mStyleMargin->mMargin; + if (aAxis == eLogicalAxisBlock + ? (aRS.mStylePosition->BSize(wm).GetUnit() == eStyleUnit_Auto && + styleMargin.GetBStartUnit(wm) != eStyleUnit_Auto && + styleMargin.GetBEndUnit(wm) != eStyleUnit_Auto) + : (aRS.mStylePosition->ISize(wm).GetUnit() == eStyleUnit_Auto && + styleMargin.GetIStartUnit(wm) != eStyleUnit_Auto && + styleMargin.GetIEndUnit(wm) != eStyleUnit_Auto)) { + nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm) + : aChildSize.ISize(wm); + nscoord gap = aCBSize - (size + marginStart + marginEnd); + if (gap > 0) { + // Note: The ComputedMax* values are always content-box max values, + // even for box-sizing:border-box. + LogicalMargin bp = aRS.ComputedLogicalBorderPadding(); + // XXX ApplySkipSides is probably not very useful here since we + // might not have created any next-in-flow yet. Use the reflow status + // instead? Do all fragments stretch? (bug 1144096). + bp.ApplySkipSides(aRS.frame->GetLogicalSkipSides()); + nscoord bpInAxis = aAxis == eLogicalAxisBlock ? bp.BStartEnd(wm) + : bp.IStartEnd(wm); + nscoord contentSize = size - bpInAxis; + NS_ASSERTION(contentSize >= 0, "huh?"); + const nscoord unstretchedContentSize = contentSize; + contentSize += gap; + nscoord max = aAxis == eLogicalAxisBlock ? aRS.ComputedMaxBSize() + : aRS.ComputedMaxISize(); + if (MOZ_UNLIKELY(contentSize > max)) { + contentSize = max; + gap = contentSize - unstretchedContentSize; + } + // |gap| is now how much the content size is actually allowed to grow. + didResize = gap > 0; + if (didResize) { + (aAxis == eLogicalAxisBlock ? aContentSize->BSize(wm) + : aContentSize->ISize(wm)) = contentSize; + if (MOZ_UNLIKELY(!aSameSide)) { + offset += gap; + } + } + } + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value"); + } + if (offset != 0) { + nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm); + pos += MOZ_LIKELY(aSameSide) ? offset : -offset; + } + return didResize; +} + +static bool +SameSide(WritingMode aContainerWM, LogicalSide aContainerSide, + WritingMode aChildWM, LogicalSide aChildSide) +{ + MOZ_ASSERT(aContainerWM.PhysicalAxis(GetAxis(aContainerSide)) == + aChildWM.PhysicalAxis(GetAxis(aChildSide))); + return aContainerWM.PhysicalSide(aContainerSide) == + aChildWM.PhysicalSide(aChildSide); +} + +static Maybe<LogicalAxis> +AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe<LogicalAxis> resizedAxis; + auto alignSelf = aAlignSelf; + bool overflowSafe = alignSelf & NS_STYLE_ALIGN_SAFE; + alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + MOZ_ASSERT(alignSelf != NS_STYLE_ALIGN_LEFT && + alignSelf != NS_STYLE_ALIGN_RIGHT, + "Grid's 'align-self' axis is never parallel to the container's " + "inline axis, so these should've computed to 'start' already"); + if (MOZ_UNLIKELY(alignSelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + alignSelf = NS_STYLE_ALIGN_STRETCH; + } + WritingMode childWM = aRS.GetWritingMode(); + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + // |sameSide| is true if the container's start side in this axis is the same + // as the child's start side, in the child's parallel axis. + bool sameSide = SameSide(aCBWM, eLogicalSideBStart, + childWM, isOrthogonal ? eLogicalSideIStart + : eLogicalSideBStart); + LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock; + if (AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide, + aCB.BSize(aCBWM), aRS, aSize, aContentSize, aPos)) { + resizedAxis.emplace(axis); + } + return resizedAxis; +} + +static Maybe<LogicalAxis> +JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe<LogicalAxis> resizedAxis; + auto justifySelf = aJustifySelf; + bool overflowSafe = justifySelf & NS_STYLE_JUSTIFY_SAFE; + justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS; + if (MOZ_UNLIKELY(justifySelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + justifySelf = NS_STYLE_ALIGN_STRETCH; + } + WritingMode childWM = aRS.GetWritingMode(); + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + // |sameSide| is true if the container's start side in this axis is the same + // as the child's start side, in the child's parallel axis. + bool sameSide = SameSide(aCBWM, eLogicalSideIStart, + childWM, isOrthogonal ? eLogicalSideBStart + : eLogicalSideIStart); + // Grid's 'justify-self' axis is always parallel to the container's inline + // axis, so justify-self:left|right always applies. + switch (justifySelf) { + case NS_STYLE_JUSTIFY_LEFT: + justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_START + : NS_STYLE_JUSTIFY_END; + break; + case NS_STYLE_JUSTIFY_RIGHT: + justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END + : NS_STYLE_JUSTIFY_START; + break; + } + + LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; + if (AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide, + aCB.ISize(aCBWM), aRS, aSize, aContentSize, aPos)) { + resizedAxis.emplace(axis); + } + return resizedAxis; +} + +static uint16_t +GetAlignJustifyValue(uint16_t aAlignment, const WritingMode aWM, + const bool aIsAlign, bool* aOverflowSafe) +{ + *aOverflowSafe = aAlignment & NS_STYLE_ALIGN_SAFE; + aAlignment &= (NS_STYLE_ALIGN_ALL_BITS & ~NS_STYLE_ALIGN_FLAG_BITS); + + // Map some alignment values to 'start' / 'end'. + switch (aAlignment) { + case NS_STYLE_ALIGN_LEFT: + case NS_STYLE_ALIGN_RIGHT: { + MOZ_ASSERT(!aIsAlign, "Grid container's 'align-contents' axis is never " + "parallel to its inline axis, so these should've computed to " + "'start' already"); + bool isStart = aWM.IsBidiLTR() == (aAlignment == NS_STYLE_ALIGN_LEFT); + return isStart ? NS_STYLE_ALIGN_START : NS_STYLE_ALIGN_END; + } + case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid + return NS_STYLE_ALIGN_START; + case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid + return NS_STYLE_ALIGN_END; + } + return aAlignment; +} + +static uint16_t +GetAlignJustifyFallbackIfAny(uint16_t aAlignment, const WritingMode aWM, + const bool aIsAlign, bool* aOverflowSafe) +{ + uint16_t fallback = aAlignment >> NS_STYLE_ALIGN_ALL_SHIFT; + if (fallback) { + return GetAlignJustifyValue(fallback, aWM, aIsAlign, aOverflowSafe); + } + // https://drafts.csswg.org/css-align-3/#fallback-alignment + switch (aAlignment) { + case NS_STYLE_ALIGN_STRETCH: + case NS_STYLE_ALIGN_SPACE_BETWEEN: + return NS_STYLE_ALIGN_START; + case NS_STYLE_ALIGN_SPACE_AROUND: + case NS_STYLE_ALIGN_SPACE_EVENLY: + return NS_STYLE_ALIGN_CENTER; + } + return 0; +} + //---------------------------------------------------------------------- // Frame class boilerplate // ======================= NS_QUERYFRAME_HEAD(nsGridContainerFrame) NS_QUERYFRAME_ENTRY(nsGridContainerFrame) NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) @@ -865,43 +1160,32 @@ nsGridContainerFrame::GridItemCB(nsIFram return *cb; } void nsGridContainerFrame::AddImplicitNamedAreas( const nsTArray<nsTArray<nsString>>& aLineNameLists) { // http://dev.w3.org/csswg/css-grid/#implicit-named-areas - // XXX this just checks x-start .. x-end in one dimension and there's - // no other error checking. A few wrong cases (maybe): - // (x-start x-end) - // (x-start) 0 (x-start) 0 (x-end) - // (x-end) 0 (x-start) 0 (x-end) - // (x-start) 0 (x-end) 0 (x-start) 0 (x-end) + // Note: recording these names for fast lookup later is just an optimization. const uint32_t len = std::min(aLineNameLists.Length(), size_t(nsStyleGridLine::kMaxLine)); nsTHashtable<nsStringHashKey> currentStarts; ImplicitNamedAreas* areas = GetImplicitNamedAreas(); for (uint32_t i = 0; i < len; ++i) { - const nsTArray<nsString>& names(aLineNameLists[i]); - const uint32_t jLen = names.Length(); - for (uint32_t j = 0; j < jLen; ++j) { - const nsString& name = names[j]; + for (const nsString& name : aLineNameLists[i]) { uint32_t index; - if (::IsNameWithStartSuffix(name, &index)) { - currentStarts.PutEntry(nsDependentSubstring(name, 0, index)); - } else if (::IsNameWithEndSuffix(name, &index)) { + if (::IsNameWithStartSuffix(name, &index) || + ::IsNameWithEndSuffix(name, &index)) { nsDependentSubstring area(name, 0, index); - if (currentStarts.Contains(area)) { - if (!areas) { - areas = new ImplicitNamedAreas; - Properties().Set(ImplicitNamedAreasProperty(), areas); - } - areas->PutEntry(area); + if (!areas) { + areas = new ImplicitNamedAreas; + Properties().Set(ImplicitNamedAreasProperty(), areas); } + areas->PutEntry(area); } } } } void nsGridContainerFrame::InitImplicitNamedAreas(const nsStylePosition* aStyle) { @@ -951,18 +1235,16 @@ nsGridContainerFrame::ResolveLine( nsAutoString lineName(aLine.mLineName); if (aSide == eLineRangeSideStart) { lineName.AppendLiteral("-start"); implicitLine = area ? area->*aAreaStart : 0; } else { lineName.AppendLiteral("-end"); implicitLine = area ? area->*aAreaEnd : 0; } - // XXX must Implicit Named Areas have all four lines? - // http://dev.w3.org/csswg/css-grid/#implicit-named-areas line = ::FindNamedLine(lineName, &aNth, aFromIndex, implicitLine, aLineNameList); } } if (line == 0) { // If mLineName ends in -start/-end, try the prefix as a named area. uint32_t implicitLine = 0; @@ -1025,17 +1307,18 @@ nsGridContainerFrame::ResolveLineRangeHe // span <integer> / auto return LinePair(kAutoLine, aStart.mInteger); } // span <custom-ident> / span * // span <custom-ident> / auto return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1? } - auto end = ResolveLine(aEnd, aEnd.mInteger, 0, aLineNameList, aAreaStart, + uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0; + auto end = ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart, aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle); int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger; if (end <= 1) { // The end is at or before the first explicit line, thus all lines before // it match <custom-ident> since they're implicit. int32_t start = std::max(end - span, nsStyleGridLine::kMinLine); return LinePair(start, end); @@ -1058,43 +1341,46 @@ nsGridContainerFrame::ResolveLineRangeHe MOZ_ASSERT(aEnd.mInteger != 0); return LinePair(start, aEnd.mInteger); } // http://dev.w3.org/csswg/css-grid/#grid-placement-errors // auto / span <custom-ident> return LinePair(start, 1); // XXX subgrid explicit size instead of 1? } } else { - start = ResolveLine(aStart, aStart.mInteger, 0, aLineNameList, aAreaStart, - aAreaEnd, aExplicitGridEnd, eLineRangeSideStart, - aStyle); + uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd : 0; + start = ResolveLine(aStart, aStart.mInteger, from, aLineNameList, + aAreaStart, aAreaEnd, aExplicitGridEnd, + eLineRangeSideStart, aStyle); if (aEnd.IsAuto()) { // A "definite line / auto" should resolve the auto to 'span 1'. // The error handling in ResolveLineRange will make that happen and also // clamp the end line correctly if we return "start / start". return LinePair(start, start); } } - uint32_t from = 0; + uint32_t from; int32_t nth = aEnd.mInteger == 0 ? 1 : aEnd.mInteger; if (aEnd.mHasSpan) { if (MOZ_UNLIKELY(start < 0)) { if (aEnd.mLineName.IsEmpty()) { return LinePair(start, start + nth); } - // Fall through and start searching from the start of the grid (from=0). + from = 0; } else { if (start >= int32_t(aExplicitGridEnd)) { // The start is at or after the last explicit line, thus all lines // after it match <custom-ident> since they're implicit. return LinePair(start, std::min(start + nth, nsStyleGridLine::kMaxLine)); } from = start; } + } else { + from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0; } auto end = ResolveLine(aEnd, nth, from, aLineNameList, aAreaStart, aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle); if (start == int32_t(kAutoLine)) { // auto / definite line start = std::max(nsStyleGridLine::kMinLine, end - 1); } return LinePair(start, end); @@ -1114,22 +1400,26 @@ nsGridContainerFrame::ResolveLineRange( aAreaEnd, aExplicitGridEnd, aStyle); MOZ_ASSERT(r.second != int32_t(kAutoLine)); if (r.first == int32_t(kAutoLine)) { // r.second is a span, clamp it to kMaxLine - 1 so that the returned // range has a HypotheticalEnd <= kMaxLine. // http://dev.w3.org/csswg/css-grid/#overlarge-grids r.second = std::min(r.second, nsStyleGridLine::kMaxLine - 1); - } else if (r.second <= r.first) { + } else { // http://dev.w3.org/csswg/css-grid/#grid-placement-errors - if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) { - r.first = nsStyleGridLine::kMaxLine - 1; + if (r.first > r.second) { + Swap(r.first, r.second); + } else if (r.first == r.second) { + if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) { + r.first = nsStyleGridLine::kMaxLine - 1; + } + r.second = r.first + 1; // XXX subgrid explicit size instead of 1? } - r.second = r.first + 1; // XXX subgrid explicit size instead of 1? } return LineRange(r.first, r.second); } nsGridContainerFrame::GridArea nsGridContainerFrame::PlaceDefinite(nsIFrame* aChild, const nsStylePosition* aStyle) { @@ -1643,40 +1933,32 @@ nsGridContainerFrame::Tracks::Initialize i += j; for (; i < mSizes.Length(); ++i) { mSizes[i].Initialize(percentageBasis, aFunctions.mAutoMinSizing, aFunctions.mAutoMaxSizing); } } -static nscoord -MinSize(nsIFrame* aChild, nsRenderingContext* aRC, WritingMode aCBWM, - LogicalAxis aAxis, nsLayoutUtils::IntrinsicISizeType aConstraint) -{ - PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); - return nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild, - aConstraint); -} - /** * Return the [min|max]-content contribution of aChild to its parent (i.e. * the child's margin-box) in aAxis. */ static nscoord ContentContribution(nsIFrame* aChild, const nsHTMLReflowState* aReflowState, nsRenderingContext* aRC, WritingMode aCBWM, LogicalAxis aAxis, - nsLayoutUtils::IntrinsicISizeType aConstraint) + nsLayoutUtils::IntrinsicISizeType aConstraint, + uint32_t aFlags = 0) { PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, aChild, aConstraint, - nsLayoutUtils::BAIL_IF_REFLOW_NEEDED); + aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED); if (size == NS_INTRINSIC_WIDTH_UNKNOWN) { // We need to reflow the child to find its BSize contribution. WritingMode wm = aChild->GetWritingMode(); nsContainerFrame* parent = aChild->GetParent(); nsPresContext* pc = aChild->PresContext(); Maybe<nsHTMLReflowState> dummyParentState; const nsHTMLReflowState* rs = aReflowState; if (!aReflowState) { @@ -1730,16 +2012,49 @@ MaxContentContribution(nsIFrame* nsRenderingContext* aRC, WritingMode aCBWM, LogicalAxis aAxis) { return ContentContribution(aChild, aRS, aRC, aCBWM, aAxis, nsLayoutUtils::PREF_ISIZE); } +static nscoord +MinSize(nsIFrame* aChild, + const nsHTMLReflowState* aRS, + nsRenderingContext* aRC, + WritingMode aCBWM, + LogicalAxis aAxis) +{ + PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); + const nsStylePosition* stylePos = aChild->StylePosition(); + const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth + : stylePos->mMinHeight; + // https://drafts.csswg.org/css-grid/#min-size-auto + // This calculates the min-content contribution from either a definite + // min-width (or min-height depending on aAxis), or the "specified / + // transferred size" for min-width:auto if overflow == visible (as min-width:0 + // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values + // (which results in always taking the "content size" part below). + nscoord sz = + nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild, + nsLayoutUtils::MIN_ISIZE); + auto unit = style.GetUnit(); + if (unit == eStyleUnit_Enumerated || + (unit == eStyleUnit_Auto && + aChild->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) { + // Now calculate the "content size" part and return whichever is smaller. + MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE); + sz = std::min(sz, ContentContribution(aChild, aRS, aRC, aCBWM, aAxis, + nsLayoutUtils::MIN_ISIZE, + nsLayoutUtils::MIN_INTRINSIC_ISIZE)); + } + return sz; +} + void nsGridContainerFrame::Tracks::CalculateSizes( GridReflowState& aState, nsTArray<GridItemInfo>& aGridItems, const TrackSizingFunctions& aFunctions, nscoord aContentBoxSize, LineRange GridArea::* aRange, IntrinsicISizeType aConstraint) @@ -1817,17 +2132,17 @@ nsGridContainerFrame::Tracks::ResolveInt Maybe<nscoord> minContentContribution; Maybe<nscoord> maxContentContribution; // min sizing TrackSize& sz = mSizes[aRange.mStart]; WritingMode wm = aState.mWM; const nsHTMLReflowState* rs = aState.mReflowState; nsRenderingContext* rc = &aState.mRenderingContext; if (sz.mState & TrackSize::eAutoMinSizing) { - nscoord s = MinSize(aGridItem, rc, wm, mAxis, aConstraint); + nscoord s = MinSize(aGridItem, rs, rc, wm, mAxis); sz.mBase = std::max(sz.mBase, s); } else if ((sz.mState & TrackSize::eMinContentMinSizing) || (aConstraint == nsLayoutUtils::MIN_ISIZE && (sz.mState & TrackSize::eFlexMinSizing))) { nscoord s = MinContentContribution(aGridItem, rs, rc, wm, mAxis); minContentContribution.emplace(s); sz.mBase = std::max(sz.mBase, minContentContribution.value()); } else if (sz.mState & TrackSize::eMaxContentMinSizing) { @@ -1922,17 +2237,17 @@ nsGridContainerFrame::Tracks::ResolveInt stateBitsPerSpan.SetCapacity(len); for (uint32_t i = stateBitsPerSpan.Length(); i < len; ++i) { stateBitsPerSpan.AppendElement(TrackSize::StateBits(0)); } } stateBitsPerSpan[span] |= state; nscoord minSize = 0; if (state & (flexMin | TrackSize::eIntrinsicMinSizing)) { // for 2.1 - minSize = MinSize(child, rc, wm, mAxis, aConstraint); + minSize = MinSize(child, aState.mReflowState, rc, wm, mAxis); } nscoord minContent = 0; if (state & (flexMin | TrackSize::eMinOrMaxContentMinSizing | // for 2.2 TrackSize::eIntrinsicMaxSizing)) { // for 2.5 minContent = MinContentContribution(child, aState.mReflowState, rc, wm, mAxis); } nscoord maxContent = 0; @@ -2251,51 +2566,176 @@ nsGridContainerFrame::Tracks::StretchFle if (flexLength > base) { base = flexLength; } } } } void +nsGridContainerFrame::Tracks::AlignJustifyContent( + const nsHTMLReflowState& aReflowState, + const LogicalSize& aContainerSize) +{ + if (mSizes.IsEmpty()) { + return; + } + + const bool isAlign = mAxis == eLogicalAxisBlock; + auto stylePos = aReflowState.mStylePosition; + const auto valueAndFallback = isAlign ? + stylePos->ComputedAlignContent(aReflowState.mStyleDisplay) : + stylePos->ComputedJustifyContent(aReflowState.mStyleDisplay); + WritingMode wm = aReflowState.GetWritingMode(); + bool overflowSafe; + auto alignment = ::GetAlignJustifyValue(valueAndFallback, wm, isAlign, + &overflowSafe); + if (alignment == NS_STYLE_ALIGN_AUTO) { + alignment = NS_STYLE_ALIGN_START; + } + + // Compute the free space and count auto-sized tracks. + size_t numAutoTracks = 0; + nscoord space; + if (alignment != NS_STYLE_ALIGN_START) { + nscoord trackSizeSum = 0; + for (const TrackSize& sz : mSizes) { + trackSizeSum += sz.mBase; + if (sz.mState & TrackSize::eAutoMaxSizing) { + ++numAutoTracks; + } + } + nscoord cbSize = isAlign ? aContainerSize.BSize(wm) + : aContainerSize.ISize(wm); + space = cbSize - trackSizeSum; + // Use the fallback value instead when applicable. + if (space < 0 || + (alignment == NS_STYLE_ALIGN_SPACE_BETWEEN && mSizes.Length() == 1)) { + auto fallback = ::GetAlignJustifyFallbackIfAny(valueAndFallback, wm, + isAlign, &overflowSafe); + if (fallback) { + alignment = fallback; + } + } + if (space == 0 || (space < 0 && overflowSafe)) { + // XXX check that this makes sense also for [last-]baseline (bug 1151204). + alignment = NS_STYLE_ALIGN_START; + } + } + + // Optimize the cases where we just need to set each track's position. + nscoord pos = 0; + bool distribute = true; + switch (alignment) { + case NS_STYLE_ALIGN_BASELINE: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("'NYI: baseline/last-baseline' (bug 1151204)"); // XXX + case NS_STYLE_ALIGN_START: + distribute = false; + break; + case NS_STYLE_ALIGN_END: + pos = space; + distribute = false; + break; + case NS_STYLE_ALIGN_CENTER: + pos = space / 2; + distribute = false; + break; + case NS_STYLE_ALIGN_STRETCH: + distribute = numAutoTracks != 0; + break; + } + if (!distribute) { + for (TrackSize& sz : mSizes) { + sz.mPosition = pos; + pos += sz.mBase; + } + return; + } + + // Distribute free space to/between tracks and set their position. + MOZ_ASSERT(space > 0, "should've handled that on the fallback path above"); + nscoord between, roundingError; + switch (alignment) { + case NS_STYLE_ALIGN_STRETCH: { + MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above"); + nscoord spacePerTrack; + roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack); + for (TrackSize& sz : mSizes) { +#ifdef DEBUG + space += sz.mBase; // for the assert below: space + all mBase == pos +#endif + sz.mPosition = pos; + if (!(sz.mState & TrackSize::eAutoMaxSizing)) { + pos += sz.mBase; + continue; + } + nscoord stretch = spacePerTrack; + if (roundingError) { + roundingError -= 1; + stretch += 1; + } + nscoord newBase = sz.mBase + stretch; + sz.mBase = newBase; + pos += newBase; + } + MOZ_ASSERT(pos == space && !roundingError, + "we didn't distribute all space?"); + return; + } + case NS_STYLE_ALIGN_SPACE_BETWEEN: + MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above"); + roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between); + break; + case NS_STYLE_ALIGN_SPACE_AROUND: + roundingError = NSCoordDivRem(space, mSizes.Length(), &between); + pos = between / 2; + break; + case NS_STYLE_ALIGN_SPACE_EVENLY: + roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between); + pos = between; + break; + default: + MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value"); + } + for (TrackSize& sz : mSizes) { + sz.mPosition = pos; + nscoord spacing = between; + if (roundingError) { + roundingError -= 1; + spacing += 1; + } + pos += sz.mBase + spacing; + } + MOZ_ASSERT(!roundingError, "we didn't distribute all space?"); +} + +void nsGridContainerFrame::LineRange::ToPositionAndLength( const nsTArray<TrackSize>& aTrackSizes, nscoord* aPos, nscoord* aLength) const { MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine, "expected a definite LineRange"); - nscoord pos = 0; - const uint32_t start = mStart; - uint32_t i = 0; - for (; i < start; ++i) { - pos += aTrackSizes[i].mBase; - } - *aPos = pos; - - nscoord length = 0; - const uint32_t end = mEnd; - MOZ_ASSERT(end <= aTrackSizes.Length(), "aTrackSizes isn't large enough"); - for (; i < end; ++i) { - length += aTrackSizes[i].mBase; - } - *aLength = length; + MOZ_ASSERT(mStart < mEnd); + nscoord startPos = aTrackSizes[mStart].mPosition; + const TrackSize& sz = aTrackSizes[mEnd - 1]; + *aPos = startPos; + *aLength = (sz.mPosition + sz.mBase) - startPos; } nscoord nsGridContainerFrame::LineRange::ToLength( const nsTArray<TrackSize>& aTrackSizes) const { MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine, "expected a definite LineRange"); - nscoord length = 0; - const uint32_t end = mEnd; - MOZ_ASSERT(end <= aTrackSizes.Length(), "aTrackSizes isn't large enough"); - for (uint32_t i = mStart; i < end; ++i) { - length += aTrackSizes[i].mBase; - } - return length; + MOZ_ASSERT(mStart < mEnd); + nscoord startPos = aTrackSizes[mStart].mPosition; + const TrackSize& sz = aTrackSizes[mEnd - 1]; + return (sz.mPosition + sz.mBase) - startPos; } void nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( const nsTArray<TrackSize>& aTrackSizes, nscoord aGridOrigin, nscoord* aPos, nscoord* aLength) const { // kAutoLine for abspos children contributes the corresponding edge @@ -2362,62 +2802,91 @@ nsGridContainerFrame::ReflowChildren(Gri MOZ_ASSERT(aState.mReflowState); WritingMode wm = aState.mReflowState->GetWritingMode(); const LogicalPoint gridOrigin(aContentArea.Origin(wm)); const nsSize containerSize = (aContentArea.Size(wm) + aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm); nsPresContext* pc = PresContext(); + nsStyleContext* containerSC = StyleContext(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { nsIFrame* child = *aState.mIter; const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame; LogicalRect cb(wm); if (MOZ_LIKELY(isGridItem)) { MOZ_ASSERT(mGridItems[aState.mIter.GridItemIndex()].mFrame == child, "iterator out of sync with mGridItems"); GridArea& area = mGridItems[aState.mIter.GridItemIndex()].mArea; MOZ_ASSERT(area.IsDefinite()); cb = ContainingBlockFor(aState, area); cb += gridOrigin; } else { cb = aContentArea; } WritingMode childWM = child->GetWritingMode(); LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); - nsHTMLReflowState childRS(pc, *aState.mReflowState, child, childCBSize); - const LogicalMargin margin = childRS.ComputedLogicalMargin(); - if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) { - // XXX the start of an align-self:stretch impl. Needs min-/max-bsize - // clamping though, and check the prop value is actually 'stretch'! - LogicalMargin bp = childRS.ComputedLogicalBorderPadding(); - bp.ApplySkipSides(child->GetLogicalSkipSides()); - nscoord bSize = childCBSize.BSize(childWM) - bp.BStartEnd(childWM) - - margin.BStartEnd(childWM); - childRS.SetComputedBSize(std::max(bSize, 0)); - } + LogicalSize percentBasis(childCBSize); + // XXX temporary workaround to avoid being INCOMPLETE until we have + // support for fragmentation (bug 1144096) + childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; + + Maybe<nsHTMLReflowState> childRS; // Maybe<> so we can reuse the space + childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis); // We need the width of the child before we can correctly convert // the writing-mode of its origin, so we reflow at (0, 0) using a dummy // containerSize, and then pass the correct position to FinishReflowChild. - nsHTMLReflowMetrics childSize(childRS); + Maybe<nsHTMLReflowMetrics> childSize; // Maybe<> so we can reuse the space + childSize.emplace(*childRS); nsReflowStatus childStatus; const nsSize dummyContainerSize; - ReflowChild(child, pc, childSize, childRS, childWM, LogicalPoint(childWM), + ReflowChild(child, pc, *childSize, *childRS, childWM, LogicalPoint(childWM), dummyContainerSize, 0, childStatus); LogicalPoint childPos = cb.Origin(wm).ConvertTo(childWM, wm, - containerSize - childSize.PhysicalSize() - - margin.Size(childWM).GetPhysicalSize(childWM)); - childPos.I(childWM) += margin.IStart(childWM); - childPos.B(childWM) += margin.BStart(childWM); - childRS.ApplyRelativePositioning(&childPos, containerSize); - FinishReflowChild(child, pc, childSize, &childRS, childWM, childPos, + containerSize - childSize->PhysicalSize()); + // Apply align/justify-self and reflow again if that affects the size. + if (isGridItem) { + LogicalSize oldSize = childSize->Size(childWM); // from the ReflowChild() + LogicalSize newContentSize(childWM); + auto align = childRS->mStylePosition->ComputedAlignSelf( + childRS->mStyleDisplay, containerSC); + Maybe<LogicalAxis> alignResize = + AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + auto justify = childRS->mStylePosition->ComputedJustifySelf( + childRS->mStyleDisplay, containerSC); + Maybe<LogicalAxis> justifyResize = + JustifySelf(justify, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + if (alignResize || justifyResize) { + FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, + LogicalPoint(childWM), containerSize, + NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); + childSize.reset(); // In reverse declaration order since it runs + childRS.reset(); // destructors. + childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis); + if ((alignResize && alignResize.value() == eLogicalAxisBlock) || + (justifyResize && justifyResize.value() == eLogicalAxisBlock)) { + childRS->SetComputedBSize(newContentSize.BSize(childWM)); + childRS->SetBResize(true); + } + if ((alignResize && alignResize.value() == eLogicalAxisInline) || + (justifyResize && justifyResize.value() == eLogicalAxisInline)) { + childRS->SetComputedISize(newContentSize.ISize(childWM)); + childRS->SetIResize(true); + } + childSize.emplace(*childRS); + ReflowChild(child, pc, *childSize, *childRS, childWM, + LogicalPoint(childWM), dummyContainerSize, 0, childStatus); + } + } + childRS->ApplyRelativePositioning(&childPos, containerSize); + FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, childPos, containerSize, 0); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); - // XXX deal with 'childStatus' not being COMPLETE + // XXX deal with 'childStatus' not being COMPLETE (bug 1144096) } if (IsAbsoluteContainer()) { nsFrameList children(GetChildList(GetAbsoluteListID())); if (!children.IsEmpty()) { LogicalMargin pad(aState.mReflowState->ComputedLogicalPadding()); pad.ApplySkipSides(GetLogicalSkipSides(aState.mReflowState)); // 'gridOrigin' is the origin of the grid (the start of the first track), @@ -2501,16 +2970,21 @@ nsGridContainerFrame::Reflow(nsPresConte bSize = std::max(bSize - GetConsumedBSize(), 0); LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm), bSize + bp.BStartEnd(wm)); aDesiredSize.SetSize(wm, desiredSize); aDesiredSize.SetOverflowAreasToDesiredBounds(); LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm), computedISize, bSize); + + // Apply 'align/justify-content' to the grid. + gridReflowState.mCols.AlignJustifyContent(aReflowState, contentArea.Size(wm)); + gridReflowState.mRows.AlignJustifyContent(aReflowState, contentArea.Size(wm)); + gridReflowState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll); ReflowChildren(gridReflowState, contentArea, aDesiredSize, aStatus); FinishAndStoreOverflow(&aDesiredSize); aStatus = NS_FRAME_COMPLETE; NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); } @@ -2527,18 +3001,21 @@ nsGridContainerFrame::IntrinsicISize(nsR return 0; } state.mCols.Initialize(state.mColFunctions, mGridColEnd, NS_UNCONSTRAINEDSIZE); state.mIter.Reset(); state.mCols.CalculateSizes(state, mGridItems, state.mColFunctions, NS_UNCONSTRAINEDSIZE, &GridArea::mCols, aConstraint); - TranslatedLineRange allTracks(0, mGridColEnd); - return allTracks.ToLength(state.mCols.mSizes); + nscoord length = 0; + for (const TrackSize& sz : state.mCols.mSizes) { + length += sz.mBase; + } + return length; } nscoord nsGridContainerFrame::GetMinISize(nsRenderingContext* aRC) { DISPLAY_MIN_WIDTH(this, mCachedMinISize); if (mCachedMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) { mCachedMinISize = IntrinsicISize(aRC, nsLayoutUtils::MIN_ISIZE);
--- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -77,16 +77,17 @@ public: eFrozen = 0x100, eSkipGrowUnlimited1 = 0x200, eSkipGrowUnlimited2 = 0x400, eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2, }; nscoord mBase; nscoord mLimit; + nscoord mPosition; // zero until we apply 'align/justify-content' StateBits mState; }; // @see nsAbsoluteContainingBlock::Reflow about this magic number static const nscoord VERY_LIKELY_A_GRID_CONTAINER = -123456789; NS_DECLARE_FRAME_PROPERTY(GridItemContainingBlockRect, DeleteValue<nsRect>) @@ -227,25 +228,24 @@ protected: { MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine); mStart = aStart; mEnd = aEnd; } }; /** - * Return aLine if it's inside the aMin..aMax range (inclusive), otherwise - * return kAutoLine. If the range is empty (aMin == aMax, i.e. there are - * no tracks in the grid) then aLine is outside. + * Return aLine if it's inside the aMin..aMax range (inclusive), + * otherwise return kAutoLine. */ static int32_t AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax) { MOZ_ASSERT(aMin <= aMax); - if (aLine < aMin || aLine > aMax || aMin == aMax) { + if (aLine < aMin || aLine > aMax) { return kAutoLine; } return aLine; } /** * A GridArea is the area in the grid for a grid item. * The area is represented by two LineRanges, both of which can be auto
--- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -13,17 +13,16 @@ #include "nsFrame.h" #include "nsIContent.h" #include "nsGkAtoms.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "nsFontMetrics.h" #include "nsBlockFrame.h" #include "nsLineBox.h" -#include "nsFlexContainerFrame.h" #include "nsImageFrame.h" #include "nsTableFrame.h" #include "nsTableCellFrame.h" #include "nsIPercentBSizeObserver.h" #include "nsLayoutUtils.h" #include "mozilla/Preferences.h" #include "nsFontInflationData.h" #include "StickyScrollContainer.h" @@ -615,17 +614,17 @@ nsHTMLReflowState::InitResizeFlags(nsPre (mFlags.mSpecialBSizeReflow || (frame->FirstInFlow()->GetStateBits() & NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) && (frame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { // Need to set the bit on the cell so that // mCBReflowState->IsBResize() is set correctly below when // reflowing descendant. SetBResize(true); - } else if (mCBReflowState && !nsLayoutUtils::IsNonWrapperBlock(frame)) { + } else if (mCBReflowState && frame->IsBlockWrapper()) { // XXX Is this problematic for relatively positioned inlines acting // as containing block for absolutely positioned elements? // Possibly; in that case we should at least be checking // NS_SUBTREE_DIRTY, I'd think. SetBResize(mCBReflowState->IsBResize()); } else if (ComputedBSize() == NS_AUTOHEIGHT) { if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && mCBReflowState) { @@ -2032,40 +2031,29 @@ IsSideCaption(nsIFrame* aFrame, const ns if (aStyleDisplay->mDisplay != NS_STYLE_DISPLAY_TABLE_CAPTION) { return false; } uint8_t captionSide = aFrame->StyleTableBorder()->mCaptionSide; return captionSide == NS_STYLE_CAPTION_SIDE_LEFT || captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; } -static nsFlexContainerFrame* -GetFlexContainer(nsIFrame* aFrame) -{ - nsIFrame* parent = aFrame->GetParent(); - if (!parent || - parent->GetType() != nsGkAtoms::flexContainerFrame) { - return nullptr; - } - - return static_cast<nsFlexContainerFrame*>(parent); -} - -// Flex items resolve block-axis percentage margin & padding against the flex -// container's block-size (which is the containing block block-size). +// Flex/grid items resolve block-axis percentage margin & padding against the +// containing block block-size (also for abs/fixed-pos child frames). // For everything else: the CSS21 spec requires that margin and padding // percentage values are calculated with respect to the inline-size of the // containing block, even for margin & padding in the block axis. static LogicalSize OffsetPercentBasis(const nsIFrame* aFrame, WritingMode aWM, const LogicalSize& aContainingBlockSize) { LogicalSize offsetPercentBasis = aContainingBlockSize; - if (!aFrame->IsFlexOrGridItem()) { + if (MOZ_LIKELY(!aFrame->GetParent() || + !aFrame->GetParent()->IsFlexOrGridContainer())) { offsetPercentBasis.BSize(aWM) = offsetPercentBasis.ISize(aWM); } else if (offsetPercentBasis.BSize(aWM) == NS_AUTOHEIGHT) { offsetPercentBasis.BSize(aWM) = 0; } return offsetPercentBasis; } @@ -2301,18 +2289,19 @@ nsHTMLReflowState::InitConstraints(nsPre (aFrameType == nsGkAtoms::scrollFrame && frame->GetContentInsertionFrame()->GetType() == nsGkAtoms::legendFrame) || (mCBReflowState && mCBReflowState->GetWritingMode().IsOrthogonalTo(mWritingMode)))) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); } - const nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame); - if (flexContainerFrame) { + nsIFrame* parent = frame->GetParent(); + nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr; + if (parentFrameType == nsGkAtoms::flexContainerFrame) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); // If we're inside of a flex container that needs to measure our // auto height, pass that information along to ComputeSize(). if (mFlags.mIsFlexContainerMeasuringHeight) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eUseAutoHeight); @@ -2338,21 +2327,23 @@ nsHTMLReflowState::InitConstraints(nsPre computeSizeFlags); ComputedISize() = size.ISize(wm); ComputedBSize() = size.BSize(wm); NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size"); NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE || ComputedBSize() >= 0, "Bogus block-size"); - // Exclude inline tables and flex items from the block margin calculations + // Exclude inline tables, side captions, flex and grid items from block + // margin calculations. if (isBlock && !IsSideCaption(frame, mStyleDisplay, cbwm) && mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE && - !flexContainerFrame) { + parentFrameType != nsGkAtoms::flexContainerFrame && + parentFrameType != nsGkAtoms::gridContainerFrame) { CalculateBlockSideMargins(aFrameType); } } } } static void UpdateProp(FrameProperties& aProps,
--- a/layout/generic/nsHTMLReflowState.h +++ b/layout/generic/nsHTMLReflowState.h @@ -647,19 +647,19 @@ public: * state are copied from the parent's reflow state. The remainder is computed. * * @param aPresContext Must be equal to aFrame->PresContext(). * @param aParentReflowState A reference to an nsHTMLReflowState object that * is to be the parent of this object. * @param aFrame The frame for whose reflow state is being constructed. * @param aAvailableSpace See comments for availableHeight and availableWidth * members. - * @param aContainingBlockSize An optional size, in app units, that - * is used by absolute positioning code to override default containing - * block sizes. + * @param aContainingBlockSize An optional size, in app units, specifying + * the containing block size to use instead of the default which is + * to use the aAvailableSpace. * @param aFlags A set of flags used for additional boolean parameters (see * below). */ nsHTMLReflowState(nsPresContext* aPresContext, const nsHTMLReflowState& aParentReflowState, nsIFrame* aFrame, const mozilla::LogicalSize& aAvailableSpace, const mozilla::LogicalSize* aContainingBlockSize = nullptr,
--- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2948,16 +2948,17 @@ NS_PTR_TO_INT32(frame->Properties().Get( /** * Is this a flex item? (i.e. a non-abs-pos child of a flex container) */ inline bool IsFlexItem() const; /** * Is this a flex or grid item? (i.e. a non-abs-pos child of a flex/grid container) */ inline bool IsFlexOrGridItem() const; + inline bool IsFlexOrGridContainer() const; /** * @return true if this frame is used as a table caption. */ inline bool IsTableCaption() const; inline bool IsBlockInside() const; inline bool IsBlockOutside() const;
--- a/layout/generic/nsIFrameInlines.h +++ b/layout/generic/nsIFrameInlines.h @@ -15,25 +15,29 @@ bool nsIFrame::IsFlexItem() const { return GetParent() && GetParent()->GetType() == nsGkAtoms::flexContainerFrame && !(GetStateBits() & NS_FRAME_OUT_OF_FLOW); } bool +nsIFrame::IsFlexOrGridContainer() const +{ + nsIAtom* t = GetType(); + return t == nsGkAtoms::flexContainerFrame || + t == nsGkAtoms::gridContainerFrame; +} + +bool nsIFrame::IsFlexOrGridItem() const { - if (GetParent()) { - nsIAtom* t = GetParent()->GetType(); - return (t == nsGkAtoms::flexContainerFrame || - t == nsGkAtoms::gridContainerFrame) && - !(GetStateBits() & NS_FRAME_OUT_OF_FLOW); - } - return false; + return !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + GetParent() && + GetParent()->IsFlexOrGridContainer(); } bool nsIFrame::IsTableCaption() const { return StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_TABLE_CAPTION && GetParent()->StyleContext()->GetPseudo() == nsCSSAnonBoxes::tableOuter; }
--- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -5402,18 +5402,18 @@ nsTextFrame::DrawSelectionDecorations(gf relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; } else if (!weDefineSelectionUnderline) { // There is no underline style definition. return; } // If underline color is defined and that doesn't depend on the // foreground color, we should use the color directly. if (aRangeStyle.IsUnderlineColorDefined() && - aRangeStyle.IsForegroundColorDefined() && - aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor) { + (!aRangeStyle.IsForegroundColorDefined() || + aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) { color = aRangeStyle.mUnderlineColor; } // If foreground color or background color is defined, the both colors // are computed by GetSelectionTextColors(). Then, we should use its // foreground color always. The color should have sufficient contrast // with the background color. else if (aRangeStyle.IsForegroundColorDefined() || aRangeStyle.IsBackgroundColorDefined()) {
--- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -383,17 +383,19 @@ RenderFrameParent::BuildLayer(nsDisplayL if (IsTempLayerManager(aManager) || (mContainer && mContainer->Manager() != aManager)) { // This can happen if aManager is a "temporary" manager, or if the // widget's layer manager changed out from under us. We need to // FIXME handle the former case somehow, probably with an API to // draw a manager's subtree. The latter is bad bad bad, but the the // MOZ_ASSERT() above will flag it. Returning nullptr here will just // cause the shadow subtree not to be rendered. - NS_WARNING("Remote iframe not rendered"); + if (!aContainerParameters.mForEventsOnly) { + NS_WARNING("Remote iframe not rendered"); + } return nullptr; } uint64_t id = GetLayerTreeId(); if (!id) { return nullptr; }
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-1-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: auto; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible fieldset below: +<div> + <fieldset id="f">This is fieldset</fieldset> +</div> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: 0; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible fieldset below: +<div> + <fieldset id="f">This is fieldset</fieldset> +</div> +<script> +h = document.body.clientHeight; +f.style.height = "auto"; +</script> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-2-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: auto; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible button below: +<div> + <button id="f">This is button</button> +</div> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-2.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: 0; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible button below: +<div> + <button id="f">This is button</button> +</div> +<script> +h = document.body.clientHeight; +f.style.height = "auto"; +</script> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-3-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: auto; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible table below: +<div> + <table id="f">This is a table</table> +</div> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-3.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: 0; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible table below: +<div> + <table id="f">This is a table</table> +</div> +<script> +h = document.body.clientHeight; +f.style.height = "auto"; +</script> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-4-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: auto; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible select below: +<div> + <select id="f"><option>This is a select</option></select> +</div> +
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1209994-4.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<style> +div { + height: 240px; +} +#f { + height: 0; + overflow: hidden; + padding: 0; + border: none; +} +</style> +There should be a visible select below: +<div> + <select id="f"><option>This is a select</option></select> +</div> +<script> +h = document.body.clientHeight; +f.style.height = "auto"; +</script> +
--- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1932,8 +1932,12 @@ skip-if(B2G||Mulet) == 1150021-1.xul 115 fuzzy(1,74) fuzzy-if(gtkWidget,6,79) == 1174332-1.html 1174332-1-ref.html == 1179078-1.html 1179078-1-ref.html == 1179288-1.html 1179288-1-ref.html == 1190635-1.html 1190635-1-ref.html == 1202512-1.html 1202512-1-ref.html == 1202512-2.html 1202512-2-ref.html != 1207326-1.html about:blank == 1209603-1.html 1209603-1-ref.html +== 1209994-1.html 1209994-1-ref.html +== 1209994-2.html 1209994-2-ref.html +== 1209994-3.html 1209994-3-ref.html +== 1209994-4.html 1209994-4-ref.html
--- a/layout/reftests/css-display/display-contents-acid.html +++ b/layout/reftests/css-display/display-contents-acid.html @@ -27,17 +27,17 @@ .colg { display:table-column-group; } .flex { display:flex; } .iflex { display:inline-flex; } .li { display:list-item; } .ib { display:inline-block; } .inline { display:inline; } .columns { -moz-columns:2; columns:2; height:4em; } -.contents { display:contents; } +.contents { display:contents; align-items:inherit; justify-items:inherit; } .c1 { color:lime; } .c2 { background:blue; color:pink; } .c3 { color:teal; } .c4 { color:green; } .c5 { color:silver; } .c6 { color:cyan; } .c7 { color:magenta; }
--- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html @@ -25,16 +25,17 @@ body,html { color:black; background:whit } .zero-size { width:0; height:0; } .auto-size { width:auto; height:0px; } .a { position: absolute; left: 13px; top: 31px; height: 12px; width: 44px; + background: blue; } .abs { position: absolute; opacity: 0.7; width: 21px; height: 15px; } @@ -42,17 +43,17 @@ body,html { color:black; background:whit left: 1px; top: 3px; bottom: 1px; width: 28px; height: auto; } .c { right: 5px; bottom: 1px; width: 112px; height: 82px; } .d { - left: 1px; top: 27px; + left: 1px; top: 20px; width: 5px; height: 1px; } .e { right: 5px; bottom: 1px; width: 112px; height: 51px; } .f { right: 5px; top: 3px; @@ -77,74 +78,74 @@ span { } </style> </head> <body> <div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="b abs">b</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="c abs">c</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="e abs">e</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="g abs">g</span> </div> </div><div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="b abs">b</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="c abs">c</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="e abs">e</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="g abs">g</span> </div> </div><div style="float:left"> <div class="grid zero-size"> <span class="b abs" style="width:12px">b</span> </div> @@ -157,27 +158,27 @@ span { <span class="i abs">i</span> </div> <div class="grid" style="height:7px"> <span class="j abs">j</span> </div> <div class="grid" style="width:43px; height:53px"> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">a</span> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">b</span> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">c</span> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">d</span> +<span class="abs" style="left:1px; top:3px; height:11px; width:5px;">a</span> +<span class="abs" style="right:5px; top:3px; height:11px; width:42px;">b</span> +<span class="abs" style="left:1px; bottom:1px; height:58px; width:5px;">c</span> +<span class="abs" style="right:5px; bottom:1px; height:58px; width:42px;">d</span> </div> <div class="grid" style="width:43px; height:28px; border-width:0;"> -<span class="abs" style="right:48px; top:3px; height:22px; width:12px;"></span> +<span class="abs" style="right:48px; top:3px; height:11px; width:12px;"></span> </div> <div class="grid" style="width:43px; height:28px; border-width:0;"> -<span class="abs" style="left:1px; bottom:57px; height:22px; width:12px;"></span> +<span class="abs" style="left:1px; bottom:57px; height:22px; width:5px;"></span> </div> <div class="grid" style="width:43px; height:28px; border-width:0;"> <span class="abs" style="right:48px; bottom:85px; height:22px; width:12px;"></span> </div> </div> </body>
--- a/layout/reftests/css-grid/grid-abspos-items-001.html +++ b/layout/reftests/css-grid/grid-abspos-items-001.html @@ -24,16 +24,17 @@ body,html { color:black; background:whit height: 60px; } .zero-size { width:0; height:0; } .auto-size { width:auto; height:auto; } .a { grid-column: 1 / 3; grid-row: 3 / 5; + background: blue; } .abs { position: absolute; top:3px; left:1px; right:5px; bottom:1px; opacity: 0.7; } .b { @@ -80,74 +81,74 @@ span { } </style> </head> <body> <div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="b abs">b</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="c abs">c</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="e abs">e</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="g abs">g</span> </div> </div><div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <div><span class="b abs">b</span></div> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <x><span class="c abs">c</span></x> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <div><span class="e abs">e</span></div> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <x><span class="g abs">g</span></x> </div> </div><div style="float:left"> <div class="grid zero-size"> <span class="b abs">b</span> </div>
--- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html @@ -26,16 +26,17 @@ body,html { color:black; background:whit } .zero-size { width:0; height:0; } .auto-size { width:auto; height:0px; } .a { position: absolute; left: 13px; top: 31px; height: 12px; width: 44px; + background: blue; } .abs { position: absolute; opacity: 0.7; width: 21px; height: 15px; } @@ -43,17 +44,17 @@ body,html { color:black; background:whit left: 1px; top: 3px; bottom: 1px; width: 28px; height: auto; } .c { right: 5px; bottom: 1px; width: 112px; height: 82px; } .d { - left: 1px; top: 27px; + left: 1px; top: 20px; width: 5px; height: 1px; } .e { right: 5px; bottom: 1px; width: 112px; height: 51px; } .f { right: 5px; top: 3px; @@ -78,74 +79,74 @@ span { } </style> </head> <body> <div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="b abs">b</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="c abs">c</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="e abs">e</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="g abs">g</span> </div> </div><div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="b abs">b</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="c abs">c</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="e abs">e</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="g abs">g</span> </div> </div><div style="float:left"> <div class="grid zero-size"> <span class="b abs" style="width:12px">b</span> </div> @@ -158,18 +159,18 @@ span { <span class="i abs">i</span> </div> <div class="grid" style="height:7px"> <span class="j abs">j</span> </div> <div class="grid" style="width:43px; height:53px"> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">a</span> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">b</span> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">c</span> -<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">d</span> +<span class="abs" style="left:1px; top:3px; height:11px; width:5px;">a</span> +<span class="abs" style="right:5px; top:3px; height:11px; width:42px;">b</span> +<span class="abs" style="left:1px; bottom:1px; height:58px; width:5px;">c</span> +<span class="abs" style="right:5px; bottom:1px; height:58px; width:42px;">d</span> </div> </div> </body> </html>
--- a/layout/reftests/css-grid/grid-abspos-items-002.html +++ b/layout/reftests/css-grid/grid-abspos-items-002.html @@ -24,16 +24,17 @@ body,html { color:black; background:whit height: 60px; } .zero-size { width:0; height:0; } .auto-size { width:auto; height:auto; } .a { grid-column: 1 / 3; grid-row: 3 / 5; + background: blue; } .abs { position: fixed; top:3px; left:1px; right:5px; bottom:1px; opacity: 0.7; } .b { @@ -80,74 +81,74 @@ span { } </style> </head> <body> <div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="b abs">b</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="c abs">c</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="e abs">e</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="g abs">g</span> </div> </div><div style="float:left"> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <div><span class="b abs">b</span></div> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <x><span class="c abs">c</span></x> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="d abs">d</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <div><span class="e abs">e</span></div> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <span class="f abs">f</span> </div> <div class="grid"> -<span class="a">a</span> +<span class="a"></span> <x><span class="g abs">g</span></x> </div> </div><div style="float:left"> <div class="grid zero-size"> <span class="b abs">b</span> </div>
--- a/layout/reftests/css-grid/grid-abspos-items-011-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-011-ref.html @@ -1,27 +1,31 @@ <!DOCTYPE HTML> <!-- Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ --> <html><head> <meta charset="utf-8"> <title>CSS Grid Test: abs pos areas in empty grid</title> - <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id="> + <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1215099"> <style type="text/css"> div { display: block; position: relative; float: left; width: 20px; height: 20px; background: red; } +x div { + padding: 4px; + background: white; +} span { position: absolute; top:0;left:0;bottom:0;right:0; background: lime; } </style> @@ -29,26 +33,50 @@ span { <body> There should be no red areas. <br clear="all"> <div><span class="cs"></span></div> <div><span class="ce"></span></div> <div><span class="rs"></span></div> -<div><span class="rs"></span></div> +<div><span class="re"></span></div> <div><span class="cs ce"></span></div> <div><span class="cs rs"></span></div> <div><span class="cs re"></span></div> <div><span class="ce rs"></span></div> <div><span class="ce re"></span></div> <div><span class="rs re"></span></div> <div><span class="cs ce rs"></span></div> <div><span class="cs ce re"></span></div> <div><span class="rs re cs"></span></div> <div><span class="rs re ce"></span></div> <div><span class="cs ce rs re"></span></div> +<br clear="all"> +<br clear="all"> + +<x> +<div><span class="cs" style="left:4px"></span></div> +<div><span class="ce" style="right:auto;width:4px"></span></div> +<div><span class="rs" style="top:4px"></span></div> +<div><span class="re" style="bottom:auto;height:4px"></span></div> + +<div><span class="cs ce" style="left:4px;"></span></div> +<div><span class="cs rs" style="left:4px;top:4px"></span></div> +<div><span class="cs re" style="left:4px;bottom:auto;height:4px"></span></div> +<div><span class="ce rs" style="right:auto;width:4px;top:4px"></span></div> +<div><span class="ce re" style="right:auto;width:4px;bottom:auto;height:4px"></span></div> +<div><span class="rs re" style="top:4px"></span></div> + +<div><span class="cs ce rs" style="left:4px;top:4px"></span></div> +<div><span class="cs ce re" style="left:4px;bottom:auto;height:4px"></span></div> +<div><span class="rs re cs" style="left:4px;top:4px"></span></div> +<div><span class="rs re ce" style="top:4px;right:auto;width:4px"></span></div> + +<div><span class="cs ce rs re" style="left:4px;top:4px"></span></div> +</x> + </body> </html>
--- a/layout/reftests/css-grid/grid-abspos-items-011.html +++ b/layout/reftests/css-grid/grid-abspos-items-011.html @@ -1,61 +1,97 @@ <!DOCTYPE HTML> <!-- Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ --> <html><head> <meta charset="utf-8"> <title>CSS Grid Test: abs pos areas in empty grid</title> - <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id="> + <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1215099"> <link rel="help" href="http://dev.w3.org/csswg/css-grid/#abspos-items"> <link rel="match" href="grid-abspos-items-011-ref.html"> <style type="text/css"> div { display: grid; position: relative; float: left; width: 20px; height: 20px; background: red; } +div.green { + background: lime; +} +x div { + padding: 4px; + background: white; +} span { position: absolute; top:0;left:0;bottom:0;right:0; background: lime; } +span.red { + background: red; +} .cs { grid-column-start: 1; } .ce { grid-column-end: 1; } .rs { grid-row-start: 1; } .re { grid-row-end: 1; } </style> </head> <body> There should be no red areas. <br clear="all"> <div><span class="cs"></span></div> +<div class="green"><span class="ce red"></span></div> +<div><span class="rs"></span></div> +<div class="green"><span class="re red"></span></div> + +<div><span class="cs ce"></span></div> +<div><span class="cs rs"></span></div> +<div class="green"><span class="cs re red"></span></div> +<div class="green"><span class="ce rs red"></span></div> +<div class="green"><span class="ce re red"></span></div> +<div><span class="rs re"></span></div> + +<div><span class="cs ce rs"></span></div> +<div class="green"><span class="cs ce re red"></span></div> +<div><span class="rs re cs"></span></div> +<div class="green"><span class="rs re ce red"></span></div> + +<div><span class="cs ce rs re"></span></div> + +<br clear="all"> +<br clear="all"> + +<!-- the same combinations in a grid container with padding --> + +<x> +<div><span class="cs"></span></div> <div><span class="ce"></span></div> <div><span class="rs"></span></div> -<div><span class="rs"></span></div> +<div><span class="re"></span></div> <div><span class="cs ce"></span></div> <div><span class="cs rs"></span></div> <div><span class="cs re"></span></div> <div><span class="ce rs"></span></div> <div><span class="ce re"></span></div> <div><span class="rs re"></span></div> <div><span class="cs ce rs"></span></div> <div><span class="cs ce re"></span></div> <div><span class="rs re cs"></span></div> <div><span class="rs re ce"></span></div> <div><span class="cs ce rs re"></span></div> +</x> </body> </html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/css-grid/grid-align-content-001-ref.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html><head> + <meta charset="utf-8"> + <title>CSS Grid Test: align-content</title> + <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151214"> + <style type="text/css"> +html,body { + color:black; background-color:white; font-size:16px; padding:0; margin:0; +} +separator { clear:both; display:block; height:6px; } + +.grid { + display: grid; + float: left; + position: relative; + border: 1px solid; + border-block-start: 2px solid blue; + grid-template: 11px 7px 5px / 3px 5px 7px; + padding: 1px 1px 3px 2px; + margin-right: 4px; + width: 40px; + height: 40px; +} + +item1,item2,item3 { + display: block; + position: relative; + background: grey; + justify-self: stretch; + align-self: stretch; +} + +item1 { grid-area: 1 / 1; } +item2 { grid-area: 2 / 2; } +item3 { grid-area: 3 / 3; } + +.hl { writing-mode: horizontal-tb; direction:ltr; } +.hr { writing-mode: horizontal-tb; direction:rtl; } +.vl { writing-mode: vertical-lr; } +.vr { writing-mode: vertical-rl; } +.vlr { writing-mode: vertical-lr; direction:rtl; } +.vrl { writing-mode: vertical-rl; direction:ltr; } + +.aend *, .aflexend * { offset-block-start:25px; } +.acenter * { offset-block-start:12.5px; } + + +.aspace-between item2 { offset-block-start:12.5px; } +.aspace-between item3 { offset-block-start:25px; } + +.aspace-around item1 { offset-block-start:4.1666px; } +.aspace-around item2 { offset-block-start:12.5px; } +.aspace-around item3 { offset-block-start:20.8333px; } + +.aspace-evenly item1 { offset-block-start:6.25px; } +.aspace-evenly item2 { offset-block-start:12.5px; } +.aspace-evenly item3 { offset-block-start:18.75px; } + +.astretch2 { grid-template-rows: 1fr 5px 7px; } +.astretch3 { grid-template-rows: 15.5px 17.5px 7px; } +.astretch4 { grid-template-rows: 11.33333px 13.33333px 15.33333px; } + +</style> +</head> +<body> + +<script> +var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ]; +var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right", + "space-between", "space-around", "space-evenly", + "stretch1", "stretch2", "stretch3", "stretch4" ]; +for (var k = 0; k < test.length; ++k) { + for (var i = 0; i < gridwm.length; ++i) { + var div = document.createElement("div"); + div.className = "grid a" + test[k] + " " + gridwm[i]; + div.appendChild(document.createElement("item1")); + div.appendChild(document.createElement("item2")); + div.appendChild(document.createElement("item3")); + document.body.appendChild(div) + } + document.body.appendChild(document.createElement("separator")); +} +</script> + + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/css-grid/grid-align-content-001.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html><head> + <meta charset="utf-8"> + <title>CSS Grid Test: align-content</title> + <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151214"> + <link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content"> + <link rel="match" href="grid-align-content-001-ref.html"> + <style type="text/css"> +html,body { + color:black; background-color:white; font-size:16px; padding:0; margin:0; +} +separator { clear:both; display:block; height:6px; } + +.grid { + display: grid; + float: left; + position: relative; + border: 1px solid; + border-block-start: 2px solid blue; + grid-template: 11px 7px 5px / 3px 5px 7px; + padding: 1px 1px 3px 2px; + margin-right: 4px; + width: 40px; + height: 40px; +} + +item1,item2,item3 { + display: block; + background: grey; + justify-self: stretch; + align-self: stretch; +} + +item1 { grid-area: 1 / 1; } +item2 { grid-area: 2 / 2; } +item3 { grid-area: 3 / 3; } + +.hl { writing-mode: horizontal-tb; direction:ltr; } +.hr { writing-mode: horizontal-tb; direction:rtl; } +.vl { writing-mode: vertical-lr; } +.vr { writing-mode: vertical-rl; } +.vlr { writing-mode: vertical-lr; direction:rtl; } +.vrl { writing-mode: vertical-rl; direction:ltr; } + +.astart { align-content:start; } +.aend { align-content:end; } +.aflexstart { align-content:flex-start; } +.aflexend { align-content:flex-end; } +.acenter { align-content:center; } +.aleft { align-content:left; } +.aright { align-content:right; } + +.aspace-between{ align-content:space-between; } +.aspace-around { align-content:space-around; } +.aspace-evenly { align-content:space-evenly; } + +.astretch1, .astretch2, .astretch3, .astretch4 { align-content:stretch; } +.astretch2 { grid-template-rows: minmax(3px,auto) 5px 7px; } +.astretch3 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) 7px; } +.astretch4 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) minmax(7px,auto); } + +</style> +</head> +<body> + +<script> +var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ]; +var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right", + "space-between", "space-around", "space-evenly", + "stretch1", "stretch2", "stretch3", "stretch4" ]; +for (var k = 0; k < test.length; ++k) { + for (var i = 0; i < gridwm.length; ++i) { + var div = document.createElement("div"); + div.className = "grid a" + test[k] + " " + gridwm[i]; + div.appendChild(document.createElement("item1")); + div.appendChild(document.createElement("item2")); + div.appendChild(document.createElement("item3")); + document.body.appendChild(div) + } + document.body.appendChild(document.createElement("separator")); +} +</script> + + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html @@ -0,0 +1,111 @@ +<!DOCTYPE HTML> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html><head> + <meta charset="utf-8"> + <title>Reference: Testing 'auto' min-sizing with definite min-width/height</title> + <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176775"> + <style type="text/css"> +body,html { color:black; background:white; font-size:12px; padding:0; margin:0; } +pre { clear:both; } + +.grid { + display: grid; + border: 1px dashed silver; + grid-template-columns: minmax(0,min-content); + grid-template-rows: minmax(0,min-content) minmax(0,min-content); + float: left; + margin-bottom:20px; + align-items: start; + justify-items: start; +} +.v { + writing-mode: vertical-lr; + -webkit-writing-mode: vertical-lr; +} +.h { + writing-mode: horizontal-tb; + -webkit-writing-mode: horizontal-tb; +} +.bb { + box-sizing: border-box; +} + +span { + display: block; + background: lime; + margin: 7px 13px 50% 20%; + padding:1px 3px 10% 30%; +} + +span.v { + height: 30px; + width: 10px; + border-left: 1px solid; + border-top: 3px solid; +} + +span.h { + width: 30px; + height: 10px; + border-left: 3px solid; + border-top: 1px solid; +} + +b40 { + display: block; + width: 40px; + height: 40px; + border: 1px solid pink; + z-index: 1; position:relative; +} + +w { + position:absolute; + background: lime; + border-width: 1px 0 0 3px; + border-style: solid; + height:45px; + width:48px; + left:-3px; top:-1px; +} +.v > w { + border-width: 3px 0 0 1px; + left:-1px; top:-3px; + height:53px; + width:61px; +} +.v .h > w { + height:48px; + width:45px; +} +.v.r > w { + height:47px; + width:54px; +} +.v .v > w { + height:41px; + width:45px; +} + +.r { position:relative; } + + </style> +</head> +<body> + +<pre>horizontal container, horizontal item</pre> +<div class="grid"><span class="h"><x></x></span></div> +<div class="grid"><span class="h bb"><x></x></span></div> +<div class="grid t3"><span class="h"><x></x></span><span class="h"><x></x></span></div> +<div class="grid"><span class="h r"><w></w><b40></b40></span></div> +<pre>horizontal container, vertical item</pre> +<div class="grid"><span class="v"><x></x></span></div> +<div class="grid"><span class="v bb"><x></x></span></div> +<div class="grid t7"><span class="v"><x></x></span><span class="v"><x></x></span></div> +<div class="grid"><span class="v r"><w></w><b40></b40></span></div> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html><head> + <meta charset="utf-8"> + <title>CSS Grid Test: 'auto' min-sizing with definite min-width/height</title> + <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176775"> + <link rel="help" href="https://drafts.csswg.org/css-grid/#min-size-auto"> + <link rel="match" href="grid-auto-min-sizing-definite-001-ref.html"> + <style type="text/css"> +body,html { color:black; background:white; font-size:12px; padding:0; margin:0; } +pre { clear:both; }