diff --git a/CLOBBER b/CLOBBER index 04652348f9b..da63a8fed89 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # 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 diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index a3e72048af2..fce078d694c 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -1238,11 +1238,13 @@ Accessible::ApplyARIAState(uint64_t* aState) const *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; } @@ -1251,12 +1253,12 @@ Accessible::ApplyARIAState(uint64_t* aState) const } 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; } diff --git a/accessible/tests/mochitest/attributes/test_obj_group.html b/accessible/tests/mochitest/attributes/test_obj_group.html index 28f45ad4b83..d5fe8947167 100644 --- a/accessible/tests/mochitest/attributes/test_obj_group.html +++ b/accessible/tests/mochitest/attributes/test_obj_group.html @@ -191,6 +191,12 @@ 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); @@ -453,5 +459,11 @@
cell
+ +
+
Apples
+
Oranges
+ +
Bananas
diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html index 4c2a2a29624..4cd57fe3b38 100644 --- a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html +++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html @@ -51,7 +51,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429547 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); @@ -68,14 +68,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429547 { 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(); } @@ -101,10 +102,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429547
   
-
+
item1
item2
+
item3
diff --git a/accessible/tests/mochitest/states/test_aria.html b/accessible/tests/mochitest/states/test_aria.html index d0f268fca29..7d1ecf65083 100644 --- a/accessible/tests/mochitest/states/test_aria.html +++ b/accessible/tests/mochitest/states/test_aria.html @@ -500,7 +500,8 @@
-
+
Item 1
Item 2
Item 3
@@ -508,6 +509,7 @@
A slider
+
Item 5
- + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 9880e7fd2a7..de05b18dc89 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 68329c182a2..823d92327b0 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 3eb7d40e357..6491b89641f 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + @@ -31,7 +31,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 4d0b0ee36b9..2fd6233c15e 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 07f937d9518..7c7ab2b5e9e 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 68329c182a2..823d92327b0 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index cf4e5a0d4d4..6a089f9cbdf 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 8c1e3135da6..a56f93bd9b6 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "06de78d2c61c084956640c480280ba518b2fe29f", + "git_revision": "47da49f8206788d70d834c3a63d9245d50c89103", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "53578330b30736e8fef30dee4eccf296e2d53ca3", + "revision": "44029d7d66e6ed745dd19fc6378d1a986382fc84", "repo_path": "integration/gaia-central" } diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 320000161d3..40eb49bef56 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 4cb90a57c91..7bb92cfc091 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + @@ -32,7 +32,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index a02be4e9f64..5425a5e5a0a 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 7b35dc5f477..5a78783c27d 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -2934,8 +2934,8 @@ var SessionStoreInternal = { // 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); } diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index e3d759c60f2..c91a996daaa 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -2162,8 +2162,8 @@ if (AppConstants.MOZ_SERVICES_HEALTHREPORT) { _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; diff --git a/browser/config/tooltool-manifests/linux64/asan.manifest b/browser/config/tooltool-manifests/linux64/asan.manifest index 820177316a8..6c174581bfe 100644 --- a/browser/config/tooltool-manifests/linux64/asan.manifest +++ b/browser/config/tooltool-manifests/linux64/asan.manifest @@ -8,5 +8,13 @@ "algorithm": "sha512", "filename": "clang.tar.bz2", "unpack": true +}, +{ +"size": 12057960, +"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"setup": "setup.sh", +"unpack": true } ] diff --git a/build/sanitizers/lsan_suppressions.txt b/build/sanitizers/lsan_suppressions.txt index 44a4261f3b1..7417ad29a37 100644 --- a/build/sanitizers/lsan_suppressions.txt +++ b/build/sanitizers/lsan_suppressions.txt @@ -109,6 +109,7 @@ 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 diff --git a/build/unix/mozconfig.gtk b/build/unix/mozconfig.gtk index f1451c43462..0414ba859a0 100644 --- 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 - - # 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 +# $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" + +# Until a tooltool with bug 1188571 landed is available everywhere +$TOOLTOOL_DIR/gtk3/setup.sh diff --git a/config/rules.mk b/config/rules.mk index 138ef8f8ba3..ec9c01e0213 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -1341,7 +1341,7 @@ endif # 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)) @@ -1349,16 +1349,12 @@ 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 diff --git a/devtools/client/tilt/TiltWorkerCrafter.js b/devtools/client/tilt/TiltWorkerCrafter.js index 8c224a23d61..da31b5abf88 100644 --- a/devtools/client/tilt/TiltWorkerCrafter.js +++ b/devtools/client/tilt/TiltWorkerCrafter.js @@ -265,7 +265,8 @@ self.random = { { 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; diff --git a/devtools/shared/heapsnapshot/CensusUtils.js b/devtools/shared/heapsnapshot/CensusUtils.js index 050d96527ad..f0fb77aa08b 100644 --- a/devtools/shared/heapsnapshot/CensusUtils.js +++ b/devtools/shared/heapsnapshot/CensusUtils.js @@ -126,8 +126,8 @@ function recursiveWalk(breakdown, edge, report, visitor) { 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); } diff --git a/dom/base/ConsoleReportCollector.cpp b/dom/base/ConsoleReportCollector.cpp index 462f7242c51..05b8c755f8a 100644 --- a/dom/base/ConsoleReportCollector.cpp +++ b/dom/base/ConsoleReportCollector.cpp @@ -79,6 +79,27 @@ ConsoleReportCollector::FlushConsoleReports(nsIDocument* aDocument) } } +void +ConsoleReportCollector::FlushConsoleReports(nsIConsoleReportCollector* aCollector) +{ + MOZ_ASSERT(aCollector); + + nsTArray 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() { } diff --git a/dom/base/ConsoleReportCollector.h b/dom/base/ConsoleReportCollector.h index 942563bfb2e..ea07fa68344 100644 --- a/dom/base/ConsoleReportCollector.h +++ b/dom/base/ConsoleReportCollector.h @@ -29,6 +29,9 @@ public: void FlushConsoleReports(nsIDocument* aDocument) override; + void + FlushConsoleReports(nsIConsoleReportCollector* aCollector) override; + private: ~ConsoleReportCollector(); diff --git a/dom/base/nsContentPermissionHelper.cpp b/dom/base/nsContentPermissionHelper.cpp index 1b1c50d027d..650f85e3cc2 100644 --- a/dom/base/nsContentPermissionHelper.cpp +++ b/dom/base/nsContentPermissionHelper.cpp @@ -32,6 +32,7 @@ #include "nsIDocument.h" #include "nsIDOMEvent.h" #include "nsWeakPtr.h" +#include "ScriptSettings.h" using mozilla::Unused; // using namespace mozilla::dom; @@ -650,7 +651,10 @@ nsContentPermissionRequestProxy::Allow(JS::HandleValue aChoices) 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 obj(cx, &aChoices.toObject()); JSAutoCompartment ac(cx, obj); @@ -658,10 +662,12 @@ nsContentPermissionRequestProxy::Allow(JS::HandleValue aChoices) 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)); diff --git a/dom/base/nsIConsoleReportCollector.h b/dom/base/nsIConsoleReportCollector.h index 0b140a5f18d..7f22c4ff4c5 100644 --- a/dom/base/nsIConsoleReportCollector.h +++ b/dom/base/nsIConsoleReportCollector.h @@ -66,7 +66,7 @@ public: 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 @@ -74,6 +74,14 @@ public: // 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) diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index ea2ad69a056..f0e567f44ce 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -329,6 +329,12 @@ JSObject* GetDefaultScopeFromJSContext(JSContext *cx) 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; } diff --git a/dom/bluetooth/common/webapi/BluetoothAdapter.cpp b/dom/bluetooth/common/webapi/BluetoothAdapter.cpp index e749ca9012d..71c95bd2ca7 100644 --- a/dom/bluetooth/common/webapi/BluetoothAdapter.cpp +++ b/dom/bluetooth/common/webapi/BluetoothAdapter.cpp @@ -1318,7 +1318,7 @@ BluetoothAdapter::HandlePullVCardListingReq(const BluetoothValue& aValue) } else if (name.EqualsLiteral("searchKey")) { init.mSearchKey = static_cast(value.get_uint32_t()); } else if (name.EqualsLiteral("searchText")) { - init.mSearchValue = value.get_nsString(); + init.mSearchValue = NS_ConvertUTF8toUTF16(value.get_nsCString()); } else if (name.EqualsLiteral("maxListCount")) { init.mMaxListCount = value.get_uint32_t(); } else if (name.EqualsLiteral("listStartOffset")) { diff --git a/dom/browser-element/mochitest/mochitest-oop.ini b/dom/browser-element/mochitest/mochitest-oop.ini index 82f72246e24..17b4c805199 100644 --- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -102,6 +102,8 @@ skip-if = (toolkit == 'gonk' && !debug) [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 diff --git a/dom/canvas/CanvasImageCache.cpp b/dom/canvas/CanvasImageCache.cpp index 5925d7a7c22..260db515a56 100644 --- a/dom/canvas/CanvasImageCache.cpp +++ b/dom/canvas/CanvasImageCache.cpp @@ -21,10 +21,16 @@ 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 { @@ -32,6 +38,7 @@ struct ImageCacheEntryData { : mImage(aOther.mImage) , mILC(aOther.mILC) , mCanvas(aOther.mCanvas) + , mIsAccelerated(aOther.mIsAccelerated) , mRequest(aOther.mRequest) , mSourceSurface(aOther.mSourceSurface) , mSize(aOther.mSize) @@ -40,6 +47,7 @@ struct ImageCacheEntryData { : mImage(aKey.mImage) , mILC(nullptr) , mCanvas(aKey.mCanvas) + , mIsAccelerated(aKey.mIsAccelerated) {} nsExpirationState* GetExpirationState() { return &mState; } @@ -50,6 +58,7 @@ struct ImageCacheEntryData { RefPtr mImage; nsIImageLoadingContent* mILC; RefPtr mCanvas; + bool mIsAccelerated; // Value nsCOMPtr mRequest; RefPtr mSourceSurface; @@ -70,46 +79,61 @@ public: 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 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(aKey)) + : mRequest(const_cast(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 mRequest; + bool mIsAccelerated; RefPtr mSourceSurface; }; @@ -130,8 +154,8 @@ public: 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 mCache; @@ -237,20 +261,22 @@ 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); @@ -267,7 +293,7 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, if (entry->mData->mRequest) { SimpleImageCacheEntry* simpleentry = - gImageCache->mSimpleCache.PutEntry(*entry->mData->mRequest); + gImageCache->mSimpleCache.PutEntry(SimpleImageCacheKey(entry->mData->mRequest, aIsAccelerated)); simpleentry->mSourceSurface = aSource; } } @@ -283,12 +309,14 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, 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; @@ -304,7 +332,8 @@ CanvasImageCache::Lookup(Element* aImage, } SourceSurface* -CanvasImageCache::SimpleLookup(Element* aImage) +CanvasImageCache::SimpleLookup(Element* aImage, + bool aIsAccelerated) { if (!gImageCache) return nullptr; @@ -319,7 +348,7 @@ CanvasImageCache::SimpleLookup(Element* aImage) if (!request) return nullptr; - SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(*request); + SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(SimpleImageCacheKey(request, aIsAccelerated)); if (!entry) return nullptr; diff --git a/dom/canvas/CanvasImageCache.h b/dom/canvas/CanvasImageCache.h index 836368c6c3b..ca38818801d 100644 --- a/dom/canvas/CanvasImageCache.h +++ b/dom/canvas/CanvasImageCache.h @@ -33,7 +33,8 @@ public: 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 @@ -43,14 +44,16 @@ public: */ 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 diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 5c366bd1e8e..05860bea8a4 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -4312,7 +4312,8 @@ CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement) return res; } - res.mSourceSurface = CanvasImageCache::SimpleLookup(aElement); + res.mSourceSurface = + CanvasImageCache::SimpleLookup(aElement, mIsSkiaGL); if (!res.mSourceSurface) { return res; } @@ -4418,7 +4419,7 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& image, } srcSurf = - CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); + CanvasImageCache::Lookup(element, mCanvasElement, &imgSize, mIsSkiaGL); } nsLayoutUtils::DirectDrawInfo drawInfo; @@ -4566,7 +4567,7 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& image, if (res.mSourceSurface) { if (res.mImageRequest) { CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest, - res.mSourceSurface, imgSize); + res.mSourceSurface, imgSize, mIsSkiaGL); } srcSurf = res.mSourceSurface; diff --git a/dom/canvas/test/_webgl-conformance.ini b/dom/canvas/test/_webgl-conformance.ini index 8b3cd44fdd6..c63158d8551 100644 --- a/dom/canvas/test/_webgl-conformance.ini +++ b/dom/canvas/test/_webgl-conformance.ini @@ -508,6 +508,7 @@ skip-if = os == 'android' 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') diff --git a/dom/canvas/test/webgl-conformance/mochitest-errata.ini b/dom/canvas/test/webgl-conformance/mochitest-errata.ini index 36fd04a280c..b684119aa8a 100644 --- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini +++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini @@ -108,6 +108,9 @@ fail-if = (os == 'linux') [_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 diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index c35493b67fd..5544060d22f 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -2557,6 +2557,8 @@ EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame, case WidgetWheelEvent::SCROLL_DEFAULT: if (isDeltaModePixel) { mode = nsIScrollableFrame::NORMAL; + } else if (aEvent->mFlags.mHandledByAPZ) { + mode = nsIScrollableFrame::SMOOTH_MSD; } else { mode = nsIScrollableFrame::SMOOTH; } @@ -3119,6 +3121,14 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, 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 { diff --git a/dom/icc/tests/marionette/test_icc_contact_add.js b/dom/icc/tests/marionette/test_icc_contact_add.js index 2e39580198f..4dc6c8e3d21 100644 --- a/dom/icc/tests/marionette/test_icc_contact_add.js +++ b/dom/icc/tests/marionette/test_icc_contact_add.js @@ -44,22 +44,6 @@ function testAddContact(aIcc, aType, aMozContact, aPin2) { // We only support SIM in emulator, so we don't have anr and email field. ok(aResult.tel.length == 1); ok(!aResult.email); - - // Get ICC contact for checking new contact - return aIcc.readContacts(aType) - .then((aResult) => { - let contact = aResult[aResult.length - 1]; - is(contact.name[0], aMozContact.name[0]); - // Maximum digits of the Dialling Number is 20, and maximum digits of Extension is 20. - is(contact.tel[0].value, aMozContact.tel[0].value.substring(0, 40)); - is(contact.id.substring(0, aIcc.iccInfo.iccid.length), aIcc.iccInfo.iccid); - - return contact.id; - }) - .then((aContactId) => { - // Clean up contact - return removeContact(aIcc, aContactId, aType, aPin2); - }); }, (aError) => { if (aType === "fdn" && aPin2 === undefined) { ok(aError.name === "SimPin2", @@ -67,31 +51,61 @@ function testAddContact(aIcc, aType, aMozContact, aPin2) { } else { ok(false, "Cannot add " + aType + " contact: " + aError.name); } - }) + }); } -function removeContact(aIcc, aContactId, aType, aPin2) { - log("removeContact: contactId=" + aContactId + - ", type=" + aType + ", pin2=" + aPin2); +function removeContacts(aIcc, aContacts, aType, aPin2) { + log("removeContacts: type=" + aType + ", pin2=" + aPin2); + let promise = Promise.resolve(); - let contact = new mozContact({}); - contact.id = aContactId; + // Clean up contacts + for (let i = 0; i < aContacts.length ; i++) { + let contact = new mozContact({}); + contact.id = aContacts[i].id; + promise = promise.then(() => aIcc.updateContact(aType, contact, aPin2)); + } + return promise; +} - return aIcc.updateContact(aType, contact, aPin2); +function testAddContacts(aIcc, aType, aPin2) { + let promise = Promise.resolve(); + + for (let i = 0; i < TEST_ADD_DATA.length; i++) { + let test_data = TEST_ADD_DATA[i]; + + promise = promise.then(() => testAddContact(aIcc, aType, test_data, aPin2)); + } + + // Get ICC contact for checking new contacts + promise = promise.then(() => aIcc.readContacts(aType)) + .then((aResult) => { + aResult = aResult.slice(aResult.length - TEST_ADD_DATA.length); + + for (let i = 0; i < aResult.length ; i++) { + let contact = aResult[i]; + let expectedResult = TEST_ADD_DATA[i]; + + is(contact.name[0], expectedResult.name[0]); + // Maximum digits of the Dialling Number is 20, and maximum digits of Extension is 20. + is(contact.tel[0].value, expectedResult.tel[0].value.substring(0, 40)); + is(contact.id.substring(0, aIcc.iccInfo.iccid.length), aIcc.iccInfo.iccid); + } + return removeContacts(aIcc, aResult, aType, aPin2); + }); + + + return promise; } // Start tests startTestCommon(function() { let icc = getMozIcc(); - let promise = Promise.resolve(); - for (let i = 0; i < TEST_ADD_DATA.length; i++) { - let test_data = TEST_ADD_DATA[i]; + + return Promise.resolve() // Test add adn contacts - promise = promise.then(() => testAddContact(icc, "adn", test_data)) - // Test add fdn contacts - .then(() => testAddContact(icc, "fdn", test_data, "0000")) - // Test add fdn contacts without passing pin2 - .then(() => testAddContact(icc, "fdn", test_data)); - } - return promise; + .then(() => testAddContacts(icc, "adn")) + // Test add fdn contacts + .then(() => testAddContacts(icc, "fdn", "0000")) + // Test one fdn contact without passing pin2 + .then(() => testAddContact(icc, "fdn", TEST_ADD_DATA[0])); }); diff --git a/dom/icc/tests/marionette/test_icc_contact_update.js b/dom/icc/tests/marionette/test_icc_contact_update.js index 12e304bb7b3..90692720422 100644 --- a/dom/icc/tests/marionette/test_icc_contact_update.js +++ b/dom/icc/tests/marionette/test_icc_contact_update.js @@ -52,36 +52,65 @@ function testUpdateContact(aIcc, aType, aContactId, aMozContact, aExpect, aPin2) return aIcc.updateContact(aType, contact, aPin2) .then((aResult) => { - // Get ICC contact for checking expect contact - return aIcc.readContacts(aType) - .then((aResult) => { - let contact = aResult[aContactId - 1]; - - is(contact.name[0], aMozContact.name[0]); - - if (aExpect.number == null) { - is(contact.tel, null); - } else { - is(contact.tel[0].value, aExpect.number); - } - - is(contact.id, aIcc.iccInfo.iccid + aContactId); - }); - }, (aError) => { - if (aType === "fdn" && aPin2 === undefined) { - ok(aError.name === "SimPin2", - "expected error when pin2 is not provided"); - } else { - ok(false, "Cannot update " + aType + " contact: " + aError.name); - } - }); + if (aExpect.number === null) { + is(aResult.tel, null); + } else { + is(aResult.tel[0].value, aExpect.number); + ok(aResult.tel.length == 1); + } + // We only support SIM in emulator, so we don't have anr and email field. + ok(!aResult.email); + is(contact.id, aIcc.iccInfo.iccid + aContactId); + }, (aError) => { + if (aType === "fdn" && aPin2 === undefined) { + ok(aError.name === "SimPin2", + "expected error when pin2 is not provided"); + } else { + ok(false, "Cannot update " + aType + " contact: " + aError.name); + } + }); } -function revertContact(aIcc, aContact, aType, aPin2) { - log("revertContact: contact:" + JSON.stringify(aContact) + - ", type=" + aType + ", pin2=" + aPin2); +function revertContacts(aIcc, aContacts, aType, aPin2) { + log("revertContacts type=" + aType + ", pin2=" + aPin2); + let promise = Promise.resolve(); - return aIcc.updateContact(aType, aContact, aPin2); + for (let i = 0; i < aContacts.length; i++) { + let aContact = aContacts[i]; + promise = promise.then(() => aIcc.updateContact(aType, aContact, aPin2)); + } + return promise; +} + +function testUpdateContacts(aIcc, aType, aCacheContacts, aPin2) { + let promise = Promise.resolve(); + + for (let i = 0; i < TEST_UPDATE_DATA.length; i++) { + let test_data = TEST_UPDATE_DATA[i]; + promise = promise.then(() => testUpdateContact(aIcc, aType, test_data.id, + test_data.data, test_data.expect, + aPin2)); + } + + // Get ICC contact for checking expect contacts + promise = promise.then(() => aIcc.readContacts(aType)) + .then((aResult) => { + for (let i = 0; i < TEST_UPDATE_DATA.length; i++) { + let expectedResult = TEST_UPDATE_DATA[i]; + let contact = aResult[expectedResult.id - 1]; + + is(contact.name[0], expectedResult.data.name[0]); + is(contact.id, aIcc.iccInfo.iccid + expectedResult.id); + if (expectedResult.expect.number === null) { + is(contact.tel, null); + } else { + is(contact.tel[0].value, expectedResult.expect.number); + } + } + return revertContacts(aIcc, aCacheContacts, aType, aPin2); + }); + + return promise; } // Start tests @@ -98,25 +127,12 @@ startTestCommon(function() { .then((aResult) => { fdnContacts = aResult; }) - .then(() => { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_UPDATE_DATA.length; i++) { - let test_data = TEST_UPDATE_DATA[i]; - let adnContact = adnContacts[test_data.id - 1]; - let fdnContact = fdnContacts[test_data.id - 1]; - - // Test update adn contacts - promise = promise.then(() => testUpdateContact(icc, "adn", test_data.id, - test_data.data, test_data.expect)) - // Test update fdn contacts - .then(() => testUpdateContact(icc, "fdn", test_data.id, test_data.data, - test_data.expect)) - // Test update fdn contacts without passing pin2 - .then(() => testUpdateContact(icc, "fdn", test_data.id, test_data.data, - test_data.expect, "0000")) - .then(() => revertContact(icc, adnContact, "adn")) - .then(() => revertContact(icc, fdnContact, "fdn", "0000")); - } - return promise; - }); + // Test update adn contacts + .then(() => testUpdateContacts(icc, "adn", adnContacts)) + // Test update fdn contacts + .then(() => testUpdateContacts(icc, "fdn", fdnContacts, "0000")) + // Test one fdn contact without passing pin2 + .then(() => testUpdateContact(icc, "fdn", TEST_UPDATE_DATA[0].id, + TEST_UPDATE_DATA[0].data, + TEST_UPDATE_DATA[0].expect)); }); diff --git a/dom/ipc/ProcessHangMonitor.cpp b/dom/ipc/ProcessHangMonitor.cpp index 1ed9d09adf8..231ba691933 100644 --- a/dom/ipc/ProcessHangMonitor.cpp +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -459,25 +459,22 @@ HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor) 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(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 diff --git a/dom/ipc/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp index 43a98345ea9..1b1a94105d3 100644 --- a/dom/ipc/ProcessPriorityManager.cpp +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -599,37 +599,19 @@ ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject) } } -static PLDHashOperator -FreezeParticularProcessPriorityManagers( - const uint64_t& aKey, - RefPtr aValue, - void* aUserData) -{ - aValue->Freeze(); - return PL_DHASH_NEXT; -} - -static PLDHashOperator -UnfreezeParticularProcessPriorityManagers( - const uint64_t& aKey, - RefPtr 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(); + } } } diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index d20e4cb6ee1..3539f221374 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -198,14 +198,14 @@ VideoData::ShallowCopyUpdateTimestampAndDuration(const VideoData* aOther, } /* 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]; @@ -229,9 +229,9 @@ void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, aVideoImage->SetDelayedConversion(true); if (aCopyData) { - aVideoImage->SetData(data); + return aVideoImage->SetData(data); } else { - aVideoImage->SetDataNoCopy(data); + return aVideoImage->SetDataNoCopy(data); } } @@ -330,12 +330,10 @@ VideoData::Create(const VideoInfo& aInfo, "Wrong format?"); PlanarYCbCrImage* videoImage = static_cast(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 @@ -346,8 +344,10 @@ VideoData::Create(const VideoInfo& aInfo, return nullptr; } videoImage = static_cast(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(); @@ -473,7 +473,9 @@ VideoData::Create(const VideoInfo& aInfo, data.mPicSize = aPicture.Size(); data.mGraphicBuffer = aBuffer; - videoImage->SetData(data); + if (!videoImage->SetData(data)) { + return nullptr; + } return v.forget(); } diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index d66be54a6c9..6dc4f2ef61d 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -283,7 +283,7 @@ public: // 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, diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index a156346e7a5..063ad765762 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -745,6 +745,9 @@ MediaDecoder::NotifyDataEnded(nsresult aStatus) { RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction([=] () { + if (self->mShuttingDown) { + return; + } self->NotifyDownloadEnded(aStatus); if (NS_SUCCEEDED(aStatus)) { HTMLMediaElement* element = self->mOwner->GetMediaElement(); diff --git a/dom/media/VideoSegment.cpp b/dom/media/VideoSegment.cpp index adc8995d685..7ac47b5c901 100644 --- a/dom/media/VideoSegment.cpp +++ b/dom/media/VideoSegment.cpp @@ -80,7 +80,10 @@ VideoFrame::CreateBlackImage(const gfx::IntSize& aSize) 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(); } diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js index beb8854dbde..2477d29db28 100644 --- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -58,6 +58,12 @@ var gVideoTests = [ { 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 = [ diff --git a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html index 9e9dd240b16..0d723db4c47 100644 --- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html +++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html @@ -14,7 +14,7 @@ createHTML({ 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) { diff --git a/dom/media/webrtc/MediaEngineDefault.cpp b/dom/media/webrtc/MediaEngineDefault.cpp index 5885d1f30ce..e735f8d21e9 100644 --- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -250,10 +250,16 @@ MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer) 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 diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp index eac7c49f930..65a8847fca9 100644 --- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -315,7 +315,10 @@ MediaEngineRemoteVideoSource::DeliverFrame(unsigned char* buffer, 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; diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp index 577e6695ed8..e16f5d1365c 100644 --- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -1444,31 +1444,6 @@ 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(aUserData); - if (asd->actor == aData) { - asd->found = true; - return PL_DHASH_STOP; - } - return PL_DHASH_NEXT; -} - -} // namespace -#endif // DEBUG - bool PluginInstanceParent::DeallocPPluginScriptableObjectParent( PPluginScriptableObjectParent* aObject) @@ -1484,9 +1459,10 @@ PluginInstanceParent::DeallocPPluginScriptableObjectParent( } #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 diff --git a/dom/presentation/interfaces/nsITCPPresentationServer.idl b/dom/presentation/interfaces/nsITCPPresentationServer.idl index 71a002f8a62..5589dc8819c 100644 --- a/dom/presentation/interfaces/nsITCPPresentationServer.idl +++ b/dom/presentation/interfaces/nsITCPPresentationServer.idl @@ -22,16 +22,17 @@ interface nsITCPDeviceInfo: nsISupports 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|. - * 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. diff --git a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp index aefa2fbff29..b4359220a7e 100644 --- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp +++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp @@ -13,6 +13,7 @@ #include "nsComponentManagerUtils.h" #include "nsIObserverService.h" #include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" #ifdef MOZ_WIDGET_ANDROID #include "nsIPropertyBag2.h" @@ -25,16 +26,12 @@ #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 { @@ -261,20 +258,35 @@ MulticastDNSDeviceProvider::RegisterService() return NS_OK; } - MOZ_ASSERT(!mRegisterRequest); - 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. */ @@ -755,12 +767,9 @@ MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo 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; @@ -855,17 +864,13 @@ MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, // 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; diff --git a/dom/presentation/provider/TCPPresentationServer.js b/dom/presentation/provider/TCPPresentationServer.js index ec6e2a4254a..af4502451a6 100644 --- a/dom/presentation/provider/TCPPresentationServer.js +++ b/dom/presentation/provider/TCPPresentationServer.js @@ -40,16 +40,11 @@ TCPPresentationServer.prototype = { 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); @@ -70,7 +65,12 @@ TCPPresentationServer.prototype = { 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() { @@ -178,31 +178,74 @@ TCPPresentationServer.prototype = { // 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) { diff --git a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js index 4714b020496..77b59abaacc 100644 --- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js +++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js @@ -1002,7 +1002,8 @@ function serverClosed() { 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); diff --git a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js index 845c6916429..cb5313ad901 100644 --- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js +++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js @@ -183,17 +183,17 @@ function testPresentationServer() { } 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() { @@ -210,12 +210,13 @@ function oneMoreLoop() { 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 diff --git a/dom/telephony/test/marionette/head.js b/dom/telephony/test/marionette/head.js index 629e310b9f1..cf45f1a8e22 100644 --- a/dom/telephony/test/marionette/head.js +++ b/dom/telephony/test/marionette/head.js @@ -771,14 +771,21 @@ var Modem = Modems[0]; .then(() => { ok(outCall instanceof TelephonyCall, "check instance"); is(outCall.id.number, number); - is(outCall.state, "dialing"); is(outCall.serviceId, serviceId); - }) - .then(() => { + // A CDMA call goes to connected state directly when the operator find // its callee, which makes the "connected" state in CDMA calls behaves // like the "alerting" state in GSM calls. let state = Modems[serviceId].isGSM() ? "alerting" : "connected"; + + // Sometimes the dialing state is missing, see Bug 1220548. + if (outCall.state === state) { + log("got " + state + " state, dialing is missing"); + return; + } + + is(outCall.state, "dialing", "check state"); + return waitForNamedStateEvent(outCall, state); }); } @@ -801,13 +808,20 @@ var Modem = Modems[0]; ok(outCall instanceof TelephonyCall, "check instance"); ok(outCall); is(outCall.id.number, number); - is(outCall.state, "dialing"); - }) - .then(() => { + // Similar to function |dial|, a CDMA call directly goes to connected // state when the operator find its callee. let state = Modems[outCall.serviceId].isGSM() ? "alerting" : "connected"; + + // Sometimes the dialing state is missing, see Bug 1220548. + if (outCall.state === state) { + log("got " + state + " state, dialing is missing"); + return; + } + + is(outCall.state, "dialing", "check state"); + return waitForNamedStateEvent(outCall, state); }) .then(() => { @@ -835,6 +849,13 @@ var Modem = Modems[0]; ok(call instanceof TelephonyCall, "check instance"); is(call.id.number, number, "check number"); + + // Sometimes the dialing state is missing, see Bug 1220548. + if (call.state === "alerting") { + log("got alerting state, dialing is missing"); + return; + } + is(call.state, "dialing", "check call state"); return waitForNamedStateEvent(call, "alerting"); diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index efb22635221..01683e49414 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -113,10 +113,7 @@ AsyncLog(nsIInterceptedChannel *aInterceptedChannel, const nsACString& aMessageName, const nsTArray& 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 reporter = aInterceptedChannel->GetConsoleReportCollector(); if (reporter) { reporter->AddConsoleReport(nsIScriptError::errorFlag, diff --git a/embedding/components/commandhandler/nsCommandGroup.cpp b/embedding/components/commandhandler/nsCommandGroup.cpp index 69a4757986b..8619e207ad4 100644 --- a/embedding/components/commandhandler/nsCommandGroup.cpp +++ b/embedding/components/commandhandler/nsCommandGroup.cpp @@ -27,14 +27,12 @@ public: protected: virtual ~nsGroupsEnumerator(); - static PLDHashOperator HashEnum(const nsACString& aKey, - nsTArray* 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; }; @@ -92,7 +90,7 @@ nsGroupsEnumerator::GetNext(nsISupports** aResult) return NS_ERROR_FAILURE; } - char* thisGroupName = mGroupNames[mIndex]; + const char* thisGroupName = mGroupNames[mIndex]; nsCOMPtr supportsString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); @@ -104,18 +102,6 @@ nsGroupsEnumerator::GetNext(nsISupports** aResult) return CallQueryInterface(supportsString, aResult); } -/* static */ -/* return false to stop */ -PLDHashOperator -nsGroupsEnumerator::HashEnum(const nsACString& aKey, nsTArray* aData, - void* aClosure) -{ - nsGroupsEnumerator* groupsEnum = static_cast(aClosure); - groupsEnum->mGroupNames[groupsEnum->mIndex] = (char*)aKey.Data(); - groupsEnum->mIndex++; - return PL_DHASH_NEXT; -} - nsresult nsGroupsEnumerator::Initialize() { @@ -123,13 +109,16 @@ nsGroupsEnumerator::Initialize() 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; diff --git a/embedding/components/commandhandler/nsCommandManager.cpp b/embedding/components/commandhandler/nsCommandManager.cpp index 6e0274d7f40..53f6e7e408a 100644 --- a/embedding/components/commandhandler/nsCommandManager.cpp +++ b/embedding/components/commandhandler/nsCommandManager.cpp @@ -34,29 +34,19 @@ nsCommandManager::~nsCommandManager() { } -static PLDHashOperator -TraverseCommandObservers(const char* aKey, - nsCommandManager::ObserverList* aObservers, - void* aClosure) -{ - nsCycleCollectionTraversalCallback* cb = - static_cast(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) diff --git a/embedding/components/commandhandler/nsControllerCommandTable.cpp b/embedding/components/commandhandler/nsControllerCommandTable.cpp index 721188b3ae9..91075bba461 100644 --- a/embedding/components/commandhandler/nsControllerCommandTable.cpp +++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp @@ -178,18 +178,6 @@ nsControllerCommandTable::GetCommandState(const char* aCommandName, 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(aArg); - (**commands) = ToNewCString(aKey); - (*commands)++; - return PL_DHASH_NEXT; -} - NS_IMETHODIMP nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, char*** aCommands) @@ -199,7 +187,10 @@ nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, *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; } diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index e2776264822..c39d7571ad6 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -103,12 +103,6 @@ struct nsWebBrowserPersist::URIData nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut); }; -struct nsWebBrowserPersist::URIFixupData -{ - RefPtr mFlatMap; - nsCOMPtr mTargetBaseURI; -}; - // Information about the output stream struct nsWebBrowserPersist::OutputData { @@ -593,13 +587,69 @@ nsWebBrowserPersist::SerializeNextFile() // 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 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 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 @@ -652,17 +702,18 @@ nsWebBrowserPersist::SerializeNextFile() 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 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 localFile; @@ -1755,23 +1806,35 @@ nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile, void nsWebBrowserPersist::Cleanup() { mURIMap.Clear(); - mOutputMap.EnumerateRead(EnumCleanupOutputMap, this); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr 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 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(); } @@ -2319,85 +2382,72 @@ nsWebBrowserPersist::EndDownload(nsresult aResult) mEventSink = nullptr; } -struct MOZ_STACK_CLASS FixRedirectData -{ - nsCOMPtr mNewChannel; - nsCOMPtr mOriginalURI; - nsCOMPtr 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 originalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(originalURI)); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Key(); + nsCOMPtr thisChannel = do_QueryInterface(key); + nsCOMPtr thisURI; - // Enumerate through existing open channels looking for one with - // a URI matching the one specified. + thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); - FixRedirectData data; - data.mNewChannel = aNewChannel; - data.mNewChannel->GetOriginalURI(getter_AddRefs(data.mOriginalURI)); - mOutputMap.EnumerateRead(EnumFixRedirect, &data); + // 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; + mOutputMap.RemoveAndForget(key, outputData); + NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); - // If a match is found, remove the data entry with the old channel key - // and re-add it with the new channel key. + // Store data again with new channel unless told to ignore redirects + if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) { + nsCOMPtr keyPtr = do_QueryInterface(aNewChannel); + mOutputMap.Put(keyPtr, outputData.forget()); + } - if (data.mMatchingKey) - { - nsAutoPtr 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 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(aClosure); - - nsCOMPtr thisChannel = do_QueryInterface(aKey); - nsCOMPtr 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 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 @@ -2409,133 +2459,6 @@ nsWebBrowserPersist::CalcTotalProgress() } } -PLDHashOperator -nsWebBrowserPersist::EnumCalcProgress(nsISupports *aKey, OutputData *aData, void* aClosure) -{ - nsWebBrowserPersist *pthis = static_cast(aClosure); - - // only count toward total progress if destination file is local - nsCOMPtr 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(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(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(aClosure); - nsresult rv; - - // Create a URI from the key - nsAutoCString key = nsAutoCString(aKey); - nsCOMPtr 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 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 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 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(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) diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index cb01ce07655..0b8687d7988 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -136,24 +136,6 @@ private: 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 mCurrentDataPath; bool mCurrentDataPathIsRelative; nsCString mCurrentRelativePathToData; diff --git a/gfx/layers/GrallocImages.cpp b/gfx/layers/GrallocImages.cpp index 5083eb4c0fe..b4c674e86aa 100644 --- a/gfx/layers/GrallocImages.cpp +++ b/gfx/layers/GrallocImages.cpp @@ -57,7 +57,7 @@ GrallocImage::~GrallocImage() { } -void +bool GrallocImage::SetData(const Data& aData) { MOZ_ASSERT(!mTextureClient, "TextureClient is already set"); @@ -70,7 +70,7 @@ GrallocImage::SetData(const Data& aData) if (gfxPlatform::GetPlatform()->IsInGonkEmulator()) { // Emulator does not support HAL_PIXEL_FORMAT_YV12. - return; + return false; } RefPtr textureClient = @@ -88,7 +88,7 @@ GrallocImage::SetData(const Data& aData) sp graphicBuffer = textureClient->GetGraphicBuffer(); if (!result || !graphicBuffer.get()) { mTextureClient = nullptr; - return; + return false; } mTextureClient = textureClient; @@ -96,7 +96,7 @@ GrallocImage::SetData(const Data& aData) void* vaddr; if (graphicBuffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &vaddr) != OK) { - return; + return false; } uint8_t* yChannel = static_cast(vaddr); @@ -144,12 +144,14 @@ GrallocImage::SetData(const Data& aData) 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(aData.mGraphicBuffer.get()); mSize = aData.mPicSize; + return true; } /** diff --git a/gfx/layers/GrallocImages.h b/gfx/layers/GrallocImages.h index 701051175b8..606944ada3a 100644 --- a/gfx/layers/GrallocImages.h +++ b/gfx/layers/GrallocImages.h @@ -66,13 +66,13 @@ public: * 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 { diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp index f2e523a05f5..667a49a100d 100644 --- a/gfx/layers/ImageContainer.cpp +++ b/gfx/layers/ImageContainer.cpp @@ -484,7 +484,7 @@ CopyPlane(uint8_t *aDst, const uint8_t *aSrc, } } -void +bool PlanarYCbCrImage::CopyData(const Data& aData) { mData = aData; @@ -496,7 +496,7 @@ PlanarYCbCrImage::CopyData(const Data& aData) // get new buffer mBuffer = AllocateBuffer(size); if (!mBuffer) - return; + return false; // update buffer size mBufferSize = size; @@ -513,12 +513,13 @@ PlanarYCbCrImage::CopyData(const Data& aData) mData.mCbCrSize, mData.mCbCrStride, mData.mCrSkip); mSize = aData.mPicSize; + return true; } -void +bool PlanarYCbCrImage::SetData(const Data &aData) { - CopyData(aData); + return CopyData(aData); } gfxImageFormat @@ -529,11 +530,12 @@ PlanarYCbCrImage::GetOffscreenFormat() mOffscreenFormat; } -void +bool PlanarYCbCrImage::SetDataNoCopy(const Data &aData) { mData = aData; mSize = aData.mPicSize; + return true; } uint8_t* diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h index 70743ecced1..d61a87e004f 100644 --- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -656,7 +656,7 @@ public: * 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 @@ -665,7 +665,7 @@ public: * 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 @@ -709,7 +709,7 @@ protected: * * @param aData Input image data. */ - void CopyData(const Data& aData); + bool CopyData(const Data& aData); /** * Return a buffer to store image data in. diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index b99796f2675..333f946f0ea 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -1051,6 +1051,11 @@ 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); diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index ce20f82f78c..2c43a0841a5 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -3351,11 +3351,8 @@ AsyncPanZoomController::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 listener = GetGestureEventListener(); if (listener) { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index a4d46d450e5..cfa1840d403 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -834,9 +834,9 @@ public: 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(); diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index dad0363e32c..2ffe88315d0 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -651,7 +651,9 @@ InputQueue::ProcessInputBlocks() { curBlock->DropEvents(); } else if (curBlock->IsDefaultPrevented()) { curBlock->DropEvents(); - target->ResetInputState(); + if (curBlock->AsTouchBlock()) { + target->ResetTouchInputState(); + } } else { UpdateActiveApzc(curBlock->GetTargetApzc()); curBlock->HandleEvents(); @@ -677,7 +679,7 @@ void InputQueue::UpdateActiveApzc(const RefPtr& aNewActive) { if (mLastActiveApzc && mLastActiveApzc != aNewActive && mTouchCounter.GetActiveTouchCount() > 0) { - mLastActiveApzc->ResetInputState(); + mLastActiveApzc->ResetTouchInputState(); } mLastActiveApzc = aNewActive; } diff --git a/gfx/layers/basic/BasicImages.cpp b/gfx/layers/basic/BasicImages.cpp index f50f27888bc..4bd232bac92 100644 --- a/gfx/layers/basic/BasicImages.cpp +++ b/gfx/layers/basic/BasicImages.cpp @@ -48,7 +48,7 @@ public: } } - virtual void SetData(const Data& aData) override; + virtual bool SetData(const Data& aData) override; virtual void SetDelayedConversion(bool aDelayed) override { mDelayedConversion = aDelayed; } already_AddRefed GetAsSourceSurface() override; @@ -91,20 +91,20 @@ public: } }; -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()); @@ -114,7 +114,7 @@ BasicPlanarYCbCrImage::SetData(const Data& aData) 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); @@ -122,12 +122,14 @@ BasicPlanarYCbCrImage::SetData(const Data& aData) 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 diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 9a02642b4ca..459ff307444 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -1650,6 +1650,7 @@ CompositorParent::SetControllerForLayerTree(uint64_t aLayersId, /*static*/ APZCTreeManager* CompositorParent::GetAPZCTreeManager(uint64_t aLayersId) { + EnsureLayerTreeMapReady(); const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId); if (state && state->mParent) { return state->mParent->mApzcTreeManager; diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp index bd53c090809..e98aa69a8b0 100644 --- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp @@ -80,7 +80,7 @@ SharedPlanarYCbCrImage::GetAsSourceSurface() return PlanarYCbCrImage::GetAsSourceSurface(); } -void +bool SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData) { // If mTextureClient has not already been allocated (through Allocate(aData)) @@ -88,20 +88,21 @@ SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData) // 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 @@ -131,12 +132,12 @@ SharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize) 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; @@ -159,6 +160,7 @@ SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData) aData.mYSize, aData.mCbCrSize, aData.mStereoMode); + return true; } uint8_t* diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.h b/gfx/layers/ipc/SharedPlanarYCbCrImage.h index b8b4162c731..1d7acdb72a9 100644 --- a/gfx/layers/ipc/SharedPlanarYCbCrImage.h +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.h @@ -35,8 +35,8 @@ public: virtual uint8_t* GetBuffer() override; virtual already_AddRefed 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; diff --git a/gfx/src/nsCoord.h b/gfx/src/nsCoord.h index cd52244a66c..bb53611cfdd 100644 --- a/gfx/src/nsCoord.h +++ b/gfx/src/nsCoord.h @@ -11,6 +11,7 @@ #include "nsMathUtils.h" #include #include +#include #include "nsDebug.h" #include @@ -57,6 +58,22 @@ inline void VERIFY_COORD(nscoord aCoord) { #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); diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index e7e5e4dc33d..1c160e93a59 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -1297,7 +1297,7 @@ js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) size_t nameEnd = begin + fun->atom()->length(); Rooted src(cx, source->substring(cx, nameEnd, end)); - if (!AppendUseStrictSource(cx, fun, src, out)) + if (!src || !AppendUseStrictSource(cx, fun, src, out)) return nullptr; } else { Rooted src(cx, source->substring(cx, begin, end)); diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 55c3b227770..8444ed3fc4c 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -1928,6 +1928,27 @@ NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat) 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 = diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 6d6ef3449d1..2e09f9564d8 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -793,7 +793,10 @@ ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValu { 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()); } diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 8fb81334854..372c39ea4f6 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -83,7 +83,7 @@ js::obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp) /* Step 1. */ RootedId idRoot(cx); - if (!ValueToId(cx, idValue, &idRoot)) + if (!ToPropertyKey(cx, idValue, &idRoot)) return false; /* Step 2. */ @@ -531,7 +531,7 @@ js::obj_hasOwnProperty(JSContext* cx, unsigned argc, Value* vp) /* Step 1. */ RootedId idRoot(cx); - if (!ValueToId(cx, idValue, &idRoot)) + if (!ToPropertyKey(cx, idValue, &idRoot)) return false; /* Step 2. */ @@ -774,7 +774,7 @@ js::obj_defineProperty(JSContext* cx, unsigned argc, Value* vp) if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) return false; RootedId id(cx); - if (!ValueToId(cx, args.get(1), &id)) + if (!ToPropertyKey(cx, args.get(1), &id)) return false; // Steps 4-5. diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index b50f8906b1e..da397a5cb5b 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2491,8 +2491,9 @@ ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst) 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); } @@ -2641,32 +2642,31 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) 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); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 81f0eab44e7..712ec2ac683 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2323,7 +2323,6 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) 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 @@ -5289,6 +5288,15 @@ BytecodeEmitter::emitIterator() 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()); @@ -5318,7 +5326,6 @@ BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) return true; } - bool BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top) { @@ -5591,8 +5598,9 @@ BytecodeEmitter::emitForIn(ParseNode* pn, ptrdiff_t top) return true; } +/* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) { LoopStmtInfo stmtInfo(cx); pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top); @@ -5600,28 +5608,48 @@ BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t 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; + forLoopRequiresFreshening = init->isKind(PNK_LET); - 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; - } + // 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; } } @@ -5749,7 +5777,7 @@ BytecodeEmitter::emitFor(ParseNode* pn, ptrdiff_t top) 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 diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 2ce95dbb5ba..1c2cff91777 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -586,7 +586,7 @@ struct BytecodeEmitter 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); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index accde97bc76..edeb85f5702 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -408,7 +408,6 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FORIN: case PNK_FOROF: case PNK_FORHEAD: - case PNK_FRESHENBLOCK: case PNK_CLASSMETHOD: case PNK_CLASSMETHODLIST: case PNK_CLASSNAMES: @@ -1710,7 +1709,6 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo 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; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index bd045e55974..d6dd032efed 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -571,10 +571,6 @@ class FullParseHandler return new_(kind, JSOP_NOP, pn1, pn2, pn3, pos); } - ParseNode* newFreshenBlock(const TokenPos& pos) { - return new_(PNK_FRESHENBLOCK, pos); - } - ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) { TokenPos pos(begin, caseList->pn_pos.end); return new_(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList); diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index c226578e868..667afaa271c 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -374,7 +374,6 @@ class NameResolver 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)); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 694600a62ac..cb35d11ed66 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -214,7 +214,6 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) 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"); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 2785f3e2a28..5d7771cd5d7 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -165,7 +165,6 @@ class PackedScopeCoordinate F(FORIN) \ F(FOROF) \ F(FORHEAD) \ - F(FRESHENBLOCK) \ F(ARGSBODY) \ F(SPREAD) \ F(MUTATEPROTO) \ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 0d932ef1d4e..7c05d3dfe1e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4063,7 +4063,8 @@ Parser::pushLetScope(HandleStaticBlockObject blockObj, AutoPus template <> SyntaxParseHandler::Node -Parser::pushLetScope(HandleStaticBlockObject blockObj, AutoPushStmtInfoPC& stmt) +Parser::pushLetScope(HandleStaticBlockObject blockObj, + AutoPushStmtInfoPC& stmt) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; @@ -5187,19 +5188,37 @@ Parser::forStatement(YieldHandling yieldHandling) 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 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; @@ -5249,9 +5268,19 @@ Parser::forStatement(YieldHandling yieldHandling) // 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); } @@ -5269,60 +5298,10 @@ Parser::forStatement(YieldHandling yieldHandling) } 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 ; ; ) - // for (let in ) - // for (let of ) - // - // transform into roughly the same parse trees as these (using deprecated - // let-block syntax): - // - // let () { for (; ; ) } - // let () { for ( in ) } - // let () { for ( of ) } - // - // 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 '. - // - // 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 letStmt; /* used if blockObj != nullptr. */ ParseNode* pn2; /* forHead->pn_kid2 */ ParseNode* pn3; /* forHead->pn_kid3 */ ParseNodeKind headKind = PNK_FORHEAD; @@ -5393,7 +5372,7 @@ Parser::forStatement(YieldHandling yieldHandling) } } else { /* Not a declaration. */ - MOZ_ASSERT(!blockObj); + MOZ_ASSERT(!letStmt); pn2 = pn1; pn1 = nullptr; @@ -5408,27 +5387,10 @@ Parser::forStatement(YieldHandling yieldHandling) 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) @@ -5450,47 +5412,13 @@ Parser::forStatement(YieldHandling yieldHandling) 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. */ @@ -5542,7 +5470,7 @@ Parser::forStatement(YieldHandling yieldHandling) if (forLetImpliedBlock) { forLetImpliedBlock->pn_expr = forLoop; forLetImpliedBlock->pn_pos = forLoop->pn_pos; - return handler.newLetBlock(forLetDecl, forLetImpliedBlock, forLoop->pn_pos); + return forLetImpliedBlock; } return forLoop; } @@ -8293,6 +8221,7 @@ Parser::comprehensionFor(GeneratorKind comprehensionKind) 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()); diff --git a/js/src/jit-test/tests/asm.js/bug1219954.js b/js/src/jit-test/tests/asm.js/bug1219954.js new file mode 100644 index 00000000000..17069e7dc3b --- /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); diff --git a/js/src/jit-test/tests/basic/bug1219363.js b/js/src/jit-test/tests/basic/bug1219363.js new file mode 100644 index 00000000000..03e4008ab08 --- /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"); diff --git a/js/src/jit-test/tests/basic/bug646968-3.js b/js/src/jit-test/tests/basic/bug646968-3.js index ee4ed9e8506..a02cb3f682c 100644 --- 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'); diff --git a/js/src/jit-test/tests/basic/bug646968-4.js b/js/src/jit-test/tests/basic/bug646968-4.js index ffb7ffed08a..4d0e2c15fbb 100644 --- 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, ""); diff --git a/js/src/jit-test/tests/basic/bug646968-6.js b/js/src/jit-test/tests/basic/bug646968-6.js new file mode 100644 index 00000000000..11b1fa1c18e --- /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); diff --git a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js b/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js deleted file mode 100644 index d1c0764dc5d..00000000000 --- 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"); } diff --git a/js/src/jit-test/tests/basic/testLet.js b/js/src/jit-test/tests/basic/testLet.js index a79c8a521e2..da00dec2dd7 100644 --- a/js/src/jit-test/tests/basic/testLet.js +++ b/js/src/jit-test/tests/basic/testLet.js @@ -3,7 +3,8 @@ 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); @@ -136,11 +137,7 @@ 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;}'); @@ -151,8 +148,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;}'); @@ -170,28 +165,31 @@ 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}); @@ -222,6 +220,13 @@ 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() {}'); diff --git a/js/src/jit-test/tests/modules/bug-1219408.js b/js/src/jit-test/tests/modules/bug-1219408.js new file mode 100644 index 00000000000..85d70928940 --- /dev/null +++ b/js/src/jit-test/tests/modules/bug-1219408.js @@ -0,0 +1,2 @@ +// |jit-test| error: Error +parseModule("").evaluation(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1abdaf6d4a6..7ef45c20b4e 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -9299,49 +9299,6 @@ CodeGenerator::visitAtomicIsLockFree(LAtomicIsLockFree* lir) 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) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index e1efd640c63..60f18a8d59d 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -286,8 +286,6 @@ class CodeGenerator : public CodeGeneratorSpecific 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); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index dc76ce695f9..c170bd09db3 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -11869,6 +11869,11 @@ IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* na { // 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); @@ -11876,15 +11881,6 @@ IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* na 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; diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 29541853c1e..2a182e4c9e7 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -446,95 +446,6 @@ template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const A template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest, bool allowDouble, Register temp, Label* fail); -template -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 -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 void MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output) diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 44a040a7c7a..cf4cedf45c6 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1074,14 +1074,6 @@ class MacroAssembler : public MacroAssemblerSpecific } } - template - void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, - Register temp, AnyRegister output); - - template - 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, diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 4cc5825a897..72a53f5762c 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -537,7 +537,7 @@ bool OperatorIn(JSContext* cx, HandleValue key, HandleObject obj, bool* out) { RootedId id(cx); - return ValueToId(cx, key, &id) && + return ToPropertyKey(cx, key, &id) && HasProperty(cx, obj, id, out); } diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index f165ce97bcf..21ea332a1e5 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -1690,6 +1690,48 @@ CodeGeneratorARM::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStati 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 void diff --git a/js/src/jit/arm/CodeGenerator-arm.h b/js/src/jit/arm/CodeGenerator-arm.h index 8f1520bdbe0..f03c3999a50 100644 --- a/js/src/jit/arm/CodeGenerator-arm.h +++ b/js/src/jit/arm/CodeGenerator-arm.h @@ -196,6 +196,8 @@ class CodeGeneratorARM : public CodeGeneratorShared 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); diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index 4f5ae20825b..6e30fb7c856 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -4827,6 +4827,95 @@ template void js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value, const BaseIndex& mem, Register flagTemp); +template +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 +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) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index fa0da656ece..ca06e129155 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1616,6 +1616,14 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM atomicEffectOp(4, AtomicFetchXorOp, value, mem, flagTemp); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + 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 diff --git a/js/src/jit/arm64/CodeGenerator-arm64.cpp b/js/src/jit/arm64/CodeGenerator-arm64.cpp index 0b2ad6916a5..dd746d5bf35 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.cpp +++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp @@ -737,3 +737,47 @@ CodeGeneratorARM64::setReturnDoubleRegs(LiveRegisterSet* regs) 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); + } +} + diff --git a/js/src/jit/arm64/CodeGenerator-arm64.h b/js/src/jit/arm64/CodeGenerator-arm64.h index 313e1fbf858..da817fd10f7 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.h +++ b/js/src/jit/arm64/CodeGenerator-arm64.h @@ -207,6 +207,8 @@ class CodeGeneratorARM64 : public CodeGeneratorShared 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); diff --git a/js/src/jit/arm64/MacroAssembler-arm64.cpp b/js/src/jit/arm64/MacroAssembler-arm64.cpp index 7efc9470be3..50e2f203006 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.cpp +++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp @@ -260,6 +260,95 @@ MacroAssemblerCompat::breakpoint() Brk((code++) & 0xffff); } +template +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 +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. diff --git a/js/src/jit/arm64/MacroAssembler-arm64.h b/js/src/jit/arm64/MacroAssembler-arm64.h index 6434cbc079f..c6081de178a 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.h +++ b/js/src/jit/arm64/MacroAssembler-arm64.h @@ -2863,6 +2863,14 @@ class MacroAssemblerCompat : public vixl::MacroAssembler atomicEffectOp(4, AtomicFetchXorOp, value, mem); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + 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) { diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 40cff833982..c49042a18ad 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -3320,6 +3320,49 @@ CodeGeneratorX86Shared::visitSimdSelect(LSimdSelect* ins) 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 void CodeGeneratorX86Shared::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value, diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h index 2200d9152aa..1672916e7b1 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h @@ -243,6 +243,8 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared 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); diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp index d1906fb3d84..d1a2e0652bf 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp @@ -143,6 +143,96 @@ MacroAssemblerX86Shared::asMasm() const return *static_cast(this); } +template +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 +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. diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h index 406ee1171a1..15dec9c96c2 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h @@ -1455,6 +1455,14 @@ class MacroAssemblerX86Shared : public Assembler ret(); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + protected: bool buildOOLFakeExitFrame(void* fakeReturnAddr); }; diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 43c05403fa0..245a430ea4d 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -181,6 +181,7 @@ EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags 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; @@ -206,7 +207,10 @@ EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags // 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 r(pobj->lastProperty()); !r.empty(); r.popFront()) { Shape& shape = r.front(); @@ -218,12 +222,10 @@ EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags } } - // 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)) diff --git a/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js b/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js new file mode 100644 index 00000000000..c89edede2d2 --- /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); diff --git a/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js new file mode 100644 index 00000000000..cbdbe9722e7 --- /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); diff --git a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js index db36323488a..8db10a4e0a7 100644 --- a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js +++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js @@ -78,11 +78,7 @@ 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) diff --git a/js/src/tests/ecma_6/Reflect/propertyKeys.js b/js/src/tests/ecma_6/Reflect/propertyKeys.js index 9dc8423d8de..6495a96fd9a 100644 --- a/js/src/tests/ecma_6/Reflect/propertyKeys.js +++ b/js/src/tests/ecma_6/Reflect/propertyKeys.js @@ -44,6 +44,12 @@ var keys = [ [Symbol.toPrimitive](hint) { return hint; } }, expected: "string" + }, + { + value: { + [Symbol.toPrimitive](hint) { return Symbol.for(hint); } + }, + expected: Symbol.for("string") } ]; diff --git 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 index 01f896d5784..4df8682cb64 100644 --- 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 @@ -23,9 +23,9 @@ function testVarPatternCombinations(makePattSrc, makePattPatt) { 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)); } } diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp index 4dd6d408e58..57614a1386a 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -6,6 +6,8 @@ #include "vm/DateTime.h" +#include "mozilla/Atomics.h" + #include #include "jsutil.h" @@ -23,6 +25,9 @@ js::DateTimeInfo::instance; /* static */ mozilla::Atomic js::DateTimeInfo::AcquireLock::spinLock; +/* extern */ mozilla::Atomic +js::DefaultTimeZoneStatus; + static bool ComputeLocalTime(time_t local, struct tm* ptm) { @@ -312,7 +317,11 @@ 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); } diff --git a/js/src/vm/DateTime.h b/js/src/vm/DateTime.h index 5c68ca13d07..968bf76e5ac 100644 --- a/js/src/vm/DateTime.h +++ b/js/src/vm/DateTime.h @@ -200,6 +200,32 @@ class DateTimeInfo 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 +DefaultTimeZoneStatus; + } /* namespace js */ #endif /* vm_DateTime_h */ diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index d6284be6ccc..f2f99ce1f1d 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -583,7 +583,7 @@ ToIdOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue ob return false; RootedId id(cx); - if (!ValueToId(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; res.set(IdToValue(id)); @@ -608,14 +608,11 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand 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(cx, key)) { if (name->isIndex(&index)) { if (GetElementNoGC(cx, obj, receiver, index, res.address())) break; @@ -625,17 +622,11 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand } } - JSAtom* name = ToAtom(cx, key); - if (!name) + RootedId id(cx); + if (!ToPropertyKey(cx, key, &id)) + return false; + if (!GetProperty(cx, obj, receiver, id, res)) 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; - } } while (false); #if JS_HAS_NO_SUCH_METHOD @@ -673,14 +664,11 @@ GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver_, 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(cx, key)) { if (name->isIndex(&index)) { if (GetElementNoGC(cx, boxed, receiver, index, res.address())) break; @@ -690,17 +678,11 @@ GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver_, } } - JSAtom* name = ToAtom(cx, key); - if (!name) + RootedId id(cx); + if (!ToPropertyKey(cx, key, &id)) + return false; + if (!GetProperty(cx, boxed, boxed, id, res)) 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; - } } while (false); // Note: we don't call a __noSuchMethod__ hook when |this| was primitive. @@ -784,7 +766,7 @@ InitElemOperation(JSContext* cx, HandleObject obj, HandleValue idval, HandleValu MOZ_ASSERT(!obj->getClass()->setProperty); RootedId id(cx); - if (!ValueToId(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; return DefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 44df23b10d2..46a1e184161 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -2024,7 +2024,7 @@ END_CASE(JSOP_AND) #define FETCH_ELEMENT_ID(n, id) \ JS_BEGIN_MACRO \ - if (!ValueToId(cx, REGS.stackHandleAt(n), &(id))) \ + if (!ToPropertyKey(cx, REGS.stackHandleAt(n), &(id))) \ goto error; \ JS_END_MACRO @@ -2468,7 +2468,7 @@ CASE(JSOP_STRICTDELELEM) ObjectOpResult result; ReservedRooted id(&rootId0); - if (!ValueToId(cx, propval, &id)) + if (!ToPropertyKey(cx, propval, &id)) goto error; if (!DeleteProperty(cx, obj, id, result)) goto error; @@ -4265,7 +4265,7 @@ js::DeleteElementJit(JSContext* cx, HandleValue val, HandleValue index, bool* bp return false; RootedId id(cx); - if (!ValueToId(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; ObjectOpResult result; if (!DeleteProperty(cx, obj, id, result)) @@ -4301,7 +4301,7 @@ js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleV bool strict) { RootedId id(cx); - if (!ValueToId(cx, index, &id)) + if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, receiver, id, value, strict); @@ -4313,7 +4313,7 @@ js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleV { MOZ_ASSERT(pc); RootedId id(cx); - if (!ValueToId(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); @@ -4486,7 +4486,7 @@ js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, H HandleObject val) { RootedId id(cx); - if (!ValueToId(cx, idval, &id)) + if (!ToPropertyKey(cx, idval, &id)) return false; return InitGetterSetterOperation(cx, pc, obj, id, val); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 19832563f42..703eb998b56 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2023,12 +2023,15 @@ 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) \ \ diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index a0433c71fc8..dab0acb0fb7 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -1062,8 +1062,9 @@ public: 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 = @@ -2119,8 +2120,7 @@ ContainerState::GetLayerCreationHint(const nsIFrame* aAnimatedGeometryRoot) 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; @@ -2801,7 +2801,7 @@ PaintedLayerDataTree::GetParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeo } nsIFrame* agr = Builder()->FindAnimatedGeometryRootFor( - const_cast(aAnimatedGeometryRoot), Builder()->RootReferenceFrame()); + const_cast(aAnimatedGeometryRoot)); MOZ_ASSERT_IF(agr, nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), agr)); if (agr != aAnimatedGeometryRoot) { return agr; @@ -2812,7 +2812,7 @@ PaintedLayerDataTree::GetParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeo if (!parent) { return nullptr; } - return Builder()->FindAnimatedGeometryRootFor(parent, Builder()->RootReferenceFrame()); + return Builder()->FindAnimatedGeometryRootFor(parent); } void @@ -3791,8 +3791,7 @@ GetScrollClipIntersection(nsDisplayListBuilder* aBuilder, const nsIFrame* aAnima 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 @@ -3905,8 +3904,8 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) // 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 @@ -4743,6 +4742,12 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) return; } + if (!mBuilder->IsPaintingToWindow()) { + // async scrolling not possible, and async scrolling info not computed + // for this paint. + return; + } + nsAutoTArray metricsArray; if (aEntry->mBaseFrameMetrics) { metricsArray.AppendElement(*aEntry->mBaseFrameMetrics); @@ -4759,8 +4764,7 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) 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 @@ -4904,8 +4908,7 @@ ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer 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, diff --git a/layout/base/FrameLayerBuilder.h b/layout/base/FrameLayerBuilder.h index 9f8e395c894..2a1da5cc2da 100644 --- a/layout/base/FrameLayerBuilder.h +++ b/layout/base/FrameLayerBuilder.h @@ -61,6 +61,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale) : mXScale(aXScale) @@ -71,6 +72,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(false) , mDisableSubpixelAntialiasingInDescendants(false) , mInLowPrecisionDisplayPort(false) + , mForEventsOnly(false) {} ContainerLayerParameters(float aXScale, float aYScale, const nsIntPoint& aOffset, @@ -84,6 +86,7 @@ struct ContainerLayerParameters { , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree) , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants) , mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort) + , mForEventsOnly(aParent.mForEventsOnly) {} float mXScale, mYScale; @@ -112,6 +115,7 @@ struct ContainerLayerParameters { 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. diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index b3d405fa9d2..973a9c79d8b 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -5584,6 +5584,8 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, return false; } + gfxContextAutoSaveRestore autoRestore(aDestinationCtx); + IntSize blurRadius; IntSize spreadRadius; // Convert the blur and spread radius to device pixels @@ -5597,11 +5599,20 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, // 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()); + Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix()); - Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect); - Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect); - Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect); + // 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(); @@ -5612,16 +5623,11 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, 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; } diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 11dcbef8b43..4113a7d45ee 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1041,6 +1041,9 @@ nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParen // for background-attachment:fixed elements. return true; } + if (aFrame->IsTransformed()) { + return true; + } nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); if (!parent) @@ -1079,23 +1082,20 @@ nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParen 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(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; } } @@ -1108,15 +1108,14 @@ ComputeAnimatedGeometryRootFor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, } 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; } @@ -1130,8 +1129,8 @@ nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot() mAnimatedGeometryRootCache.Clear(); mCurrentAnimatedGeometryRoot = ComputeAnimatedGeometryRootFor(this, const_cast(mCurrentFrame)); - AnimatedGeometryRootLookup lookup(mCurrentFrame, nullptr); - mAnimatedGeometryRootCache.Put(lookup, mCurrentAnimatedGeometryRoot); + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), mCurrentAnimatedGeometryRoot)); + mAnimatedGeometryRootCache.Put(const_cast(mCurrentFrame), mCurrentAnimatedGeometryRoot); } void @@ -3282,6 +3281,12 @@ nsDisplayLayerEventRegions::AddFrame(nsDisplayListBuilder* aBuilder, 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 @@ -3932,9 +3937,11 @@ already_AddRefed nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters params = aContainerParameters; + params.mForEventsOnly = mForEventsOnly; RefPtr container = aManager->GetLayerBuilder()-> BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList, - aContainerParameters, nullptr, + params, nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); if (!container) return nullptr; @@ -4780,6 +4787,9 @@ nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, void nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame); mReferenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index a518e97394b..f908c89d19d 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -260,7 +260,7 @@ public: * 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 @@ -644,11 +644,11 @@ public: 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; @@ -976,12 +976,11 @@ public: 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) { @@ -1113,27 +1112,8 @@ private: // 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, nsIFrame*> - mAnimatedGeometryRootCache; + nsDataHashtable, nsIFrame*> mAnimatedGeometryRootCache; // will-change budget tracker nsDataHashtable, DocumentWillChangeBudget> mWillChangeBudget; @@ -3959,6 +3939,15 @@ public: 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); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 3f2eaf34e23..56b4be0d7c2 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1391,12 +1391,17 @@ nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame) // 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; } @@ -1866,29 +1871,38 @@ nsLayoutUtils::IsScrollbarThumbLayerized(nsIFrame* aThumbFrame) nsIFrame* nsLayoutUtils::GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder, - nsIFrame* aFrame, - const nsIFrame* aStopAtAncestor) + nsIFrame* aFrame) { - return aBuilder->FindAnimatedGeometryRootFor(aFrame, aStopAtAncestor); + 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)); + nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame, aBuilder->RootReferenceFrame()); + if (viewportFrame) { + return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame); + } } - return GetAnimatedGeometryRootForFrame(aBuilder, f, aItem->ReferenceFrame()); + if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM && + static_cast(aItem)->IsReferenceFrameBoundary() && + f != aBuilder->RootReferenceFrame()) { + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(f); + if (parent) { + return GetAnimatedGeometryRootForFrame(aBuilder, parent); + } + } + return GetAnimatedGeometryRootForFrame(aBuilder, f); } // static @@ -4532,10 +4546,15 @@ nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, 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; @@ -4751,9 +4770,9 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, 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); @@ -4763,6 +4782,46 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, 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(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); @@ -4774,18 +4833,13 @@ nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, : 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); @@ -8662,3 +8716,15 @@ nsLayoutUtils::GetSelectionBoundingRect(Selection* aSel) 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; +} diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 2480e57a620..f4cfcfa742a 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -273,10 +273,13 @@ public: * * @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 @@ -549,17 +552,24 @@ public: * 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 @@ -1314,10 +1324,13 @@ public: * 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, @@ -1333,10 +1346,20 @@ public: 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, @@ -2763,6 +2786,12 @@ public: * @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; diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 0f442bc07fa..471935d4ee7 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -1526,7 +1526,7 @@ nsFlexContainerFrame:: // 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()); } @@ -1565,8 +1565,8 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, 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, @@ -1574,6 +1574,18 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, 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 bit until we implement that + mAlignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + SetFlexBaseSizeAndMainSize(aFlexBaseSize); CheckForMinSizeAuto(aFlexItemReflowState, aAxisTracker); @@ -1592,12 +1604,6 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, } #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 @@ -1608,9 +1614,9 @@ FlexItem::FlexItem(nsHTMLReflowState& aFlexItemReflowState, // 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; } } @@ -1643,7 +1649,7 @@ FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, 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 == @@ -1833,6 +1839,7 @@ private: nscoord mPackingSpaceRemaining; uint32_t mNumAutoMarginsInMainAxis; uint32_t mNumPackingSpacesRemaining; + // XXX this should be uint16_t when we add explicit fallback handling uint8_t mJustifyContent; }; @@ -1866,6 +1873,7 @@ private: nscoord mPackingSpaceRemaining; uint32_t mNumPackingSpacesRemaining; + // XXX this should be uint16_t when we add explicit fallback handling uint8_t mAlignContent; }; @@ -2428,6 +2436,14 @@ MainAxisPositionTracker:: 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 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. @@ -2446,20 +2462,27 @@ MainAxisPositionTracker:: // 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; } } @@ -2469,25 +2492,31 @@ MainAxisPositionTracker:: 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'"); @@ -2548,8 +2577,8 @@ 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"); @@ -2581,6 +2610,14 @@ CrossAxisPositionTracker:: { 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 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 @@ -2614,21 +2651,28 @@ CrossAxisPositionTracker:: // 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; } } @@ -2636,25 +2680,33 @@ CrossAxisPositionTracker:: // 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'"); @@ -2674,7 +2726,7 @@ CrossAxisPositionTracker:: 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' " @@ -2706,8 +2758,8 @@ 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"); @@ -2742,7 +2794,7 @@ FlexLine::ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker) 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). @@ -2815,7 +2867,7 @@ FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize, // 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; @@ -2894,33 +2946,46 @@ SingleLineCrossAxisPositionTracker:: 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. @@ -3444,7 +3509,7 @@ nsFlexContainerFrame::SizeItemInCrossAxis( 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 @@ -3501,7 +3566,7 @@ nsFlexContainerFrame::SizeItemInCrossAxis( // 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()); } } @@ -3736,7 +3801,8 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, // scope of a particular flex line) CrossAxisPositionTracker crossAxisPosnTracker(lines.getFirst(), - aReflowState.mStylePosition->mAlignContent, + aReflowState.mStylePosition->ComputedAlignContent( + aReflowState.mStyleDisplay), contentBoxCrossSize, isCrossSizeDefinite, aAxisTracker); @@ -3775,7 +3841,9 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, // 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); @@ -4015,7 +4083,7 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, // 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()); diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 7a7408e2f71..754397fabd3 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4300,6 +4300,9 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, 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, @@ -4321,9 +4324,12 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, 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 @@ -4365,14 +4371,14 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, *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); @@ -4380,7 +4386,6 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, } const nsStyleCoord& minISizeCoord = stylePos->MinISize(aWM); - nscoord minISize; if (minISizeCoord.GetUnit() != eStyleUnit_Auto && !(isFlexItem && isInlineFlexItem)) { @@ -4388,6 +4393,13 @@ nsFrame::ComputeSize(nsRenderingContext *aRenderingContext, 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 diff --git a/layout/generic/nsFrameSetFrame.cpp b/layout/generic/nsFrameSetFrame.cpp index d78e8f45b01..b85e2751139 100644 --- a/layout/generic/nsFrameSetFrame.cpp +++ b/layout/generic/nsFrameSetFrame.cpp @@ -430,12 +430,12 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, int32_t fixedTotal = 0; int32_t numFixed = 0; - nsAutoArrayPtr fixed(new int32_t[aNumSpecs]); + auto fixed = MakeUnique(aNumSpecs); int32_t numPercent = 0; - nsAutoArrayPtr percent(new int32_t[aNumSpecs]); + auto percent = MakeUnique(aNumSpecs); int32_t relativeSums = 0; int32_t numRelative = 0; - nsAutoArrayPtr relative(new int32_t[aNumSpecs]); + auto relative = MakeUnique(aNumSpecs); if (MOZ_UNLIKELY(!fixed || !percent || !relative)) { return; // NS_ERROR_OUT_OF_MEMORY @@ -467,7 +467,7 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, // 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; } @@ -482,7 +482,7 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, // 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; } @@ -497,7 +497,7 @@ void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext, // 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); } } @@ -852,10 +852,10 @@ nsHTMLFramesetFrame::Reflow(nsPresContext* aPresContext, CalculateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get()); CalculateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get()); - nsAutoArrayPtr verBordersVis; // vertical borders visibility - nsAutoArrayPtr verBorderColors; - nsAutoArrayPtr horBordersVis; // horizontal borders visibility - nsAutoArrayPtr horBorderColors; + UniquePtr verBordersVis; // vertical borders visibility + UniquePtr verBorderColors; + UniquePtr horBordersVis; // horizontal borders visibility + UniquePtr horBorderColors; nscolor borderColor = GetBorderColor(); nsFrameborder frameborder = GetFrameBorder(); @@ -865,15 +865,15 @@ nsHTMLFramesetFrame::Reflow(nsPresContext* aPresContext, 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(mNumCols); + verBorderColors = MakeUnique(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(mNumRows); + horBorderColors = MakeUnique(mNumRows); for (int horX = 0; horX < mNumRows; horX++) { horBordersVis[horX] = false; horBorderColors[horX] = NO_COLOR; diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index ddfbe776a9a..884e44c22c5 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -587,6 +587,14 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::Tracks 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 { @@ -800,13 +808,18 @@ IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex) static nscoord GridLinePosition(uint32_t aLine, const nsTArray& 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; } /** @@ -829,6 +842,288 @@ GetDisplayFlagsForGridItem(nsIFrame* aFrame) 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 = '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 +AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe 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 +JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe 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 @@ -870,33 +1165,22 @@ nsGridContainerFrame::AddImplicitNamedAreas( const nsTArray>& 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 currentStarts; ImplicitNamedAreas* areas = GetImplicitNamedAreas(); for (uint32_t i = 0; i < len; ++i) { - const nsTArray& 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); } } } @@ -956,8 +1240,6 @@ nsGridContainerFrame::ResolveLine( 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); } @@ -1030,7 +1312,8 @@ nsGridContainerFrame::ResolveLineRangeHelper( 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; @@ -1063,9 +1346,10 @@ nsGridContainerFrame::ResolveLineRangeHelper( 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 @@ -1074,14 +1358,14 @@ nsGridContainerFrame::ResolveLineRangeHelper( } } - 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 @@ -1090,6 +1374,8 @@ nsGridContainerFrame::ResolveLineRangeHelper( } from = start; } + } else { + from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0; } auto end = ResolveLine(aEnd, nth, from, aLineNameList, aAreaStart, aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle); @@ -1119,12 +1405,16 @@ nsGridContainerFrame::ResolveLineRange( // 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); } @@ -1648,15 +1938,6 @@ nsGridContainerFrame::Tracks::Initialize( } } -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. @@ -1667,11 +1948,12 @@ ContentContribution(nsIFrame* aChild, 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(); @@ -1735,6 +2017,39 @@ MaxContentContribution(nsIFrame* aChild, 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, @@ -1822,7 +2137,7 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSizeStep1( 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 && @@ -1927,7 +2242,7 @@ nsGridContainerFrame::Tracks::ResolveIntrinsicSize( 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 @@ -2255,27 +2570,155 @@ nsGridContainerFrame::Tracks::StretchFlexibleTracks( } } +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& 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 @@ -2284,13 +2727,10 @@ nsGridContainerFrame::LineRange::ToLength( { 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 @@ -2367,6 +2807,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, (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; @@ -2383,36 +2824,64 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, } 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 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 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 alignResize = + AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + auto justify = childRS->mStylePosition->ComputedJustifySelf( + childRS->mStyleDisplay, containerSC); + Maybe 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()) { @@ -2506,6 +2975,11 @@ nsGridContainerFrame::Reflow(nsPresContext* aPresContext, 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); @@ -2532,8 +3006,11 @@ nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext, 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 diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h index 28091ffe7f9..3113037b18f 100644 --- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -82,6 +82,7 @@ public: nscoord mBase; nscoord mLimit; + nscoord mPosition; // zero until we apply 'align/justify-content' StateBits mState; }; @@ -232,15 +233,14 @@ protected: }; /** - * 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; diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index 5ec61ea07f1..37816b92557 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -18,7 +18,6 @@ #include "nsFontMetrics.h" #include "nsBlockFrame.h" #include "nsLineBox.h" -#include "nsFlexContainerFrame.h" #include "nsImageFrame.h" #include "nsTableFrame.h" #include "nsTableCellFrame.h" @@ -620,7 +619,7 @@ nsHTMLReflowState::InitResizeFlags(nsPresContext* aPresContext, nsIAtom* aFrameT // 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 @@ -2037,20 +2036,8 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay, 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(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. @@ -2060,7 +2047,8 @@ OffsetPercentBasis(const nsIFrame* aFrame, 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; @@ -2306,8 +2294,9 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, 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); @@ -2343,11 +2332,13 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, 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); } } diff --git a/layout/generic/nsHTMLReflowState.h b/layout/generic/nsHTMLReflowState.h index 1ab628e7d37..b802255607d 100644 --- a/layout/generic/nsHTMLReflowState.h +++ b/layout/generic/nsHTMLReflowState.h @@ -652,9 +652,9 @@ public: * @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). */ diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 7438b6813ea..1019138248b 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2953,6 +2953,7 @@ NS_PTR_TO_INT32(frame->Properties().Get(nsIFrame::ParagraphDepthProperty())) * 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. diff --git a/layout/generic/nsIFrameInlines.h b/layout/generic/nsIFrameInlines.h index e2872b2a384..d29df4e4c34 100644 --- a/layout/generic/nsIFrameInlines.h +++ b/layout/generic/nsIFrameInlines.h @@ -19,16 +19,20 @@ nsIFrame::IsFlexItem() const !(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 diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 5d080978de7..c3a2d7c9446 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -5407,8 +5407,8 @@ nsTextFrame::DrawSelectionDecorations(gfxContext* aContext, // 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 diff --git a/layout/ipc/RenderFrameParent.cpp b/layout/ipc/RenderFrameParent.cpp index 7f38a395e3d..4f1b07faa1e 100644 --- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -388,7 +388,9 @@ RenderFrameParent::BuildLayer(nsDisplayListBuilder* aBuilder, // 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; } diff --git a/layout/reftests/bugs/1209994-1-ref.html b/layout/reftests/bugs/1209994-1-ref.html new file mode 100644 index 00000000000..1b6f7cdd463 --- /dev/null +++ b/layout/reftests/bugs/1209994-1-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible fieldset below: +
+
This is fieldset
+
+ diff --git a/layout/reftests/bugs/1209994-1.html b/layout/reftests/bugs/1209994-1.html new file mode 100644 index 00000000000..d362c00872e --- /dev/null +++ b/layout/reftests/bugs/1209994-1.html @@ -0,0 +1,21 @@ + + +There should be a visible fieldset below: +
+
This is fieldset
+
+ + diff --git a/layout/reftests/bugs/1209994-2-ref.html b/layout/reftests/bugs/1209994-2-ref.html new file mode 100644 index 00000000000..8858616b69a --- /dev/null +++ b/layout/reftests/bugs/1209994-2-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible button below: +
+ +
+ diff --git a/layout/reftests/bugs/1209994-2.html b/layout/reftests/bugs/1209994-2.html new file mode 100644 index 00000000000..485b0fe5854 --- /dev/null +++ b/layout/reftests/bugs/1209994-2.html @@ -0,0 +1,21 @@ + + +There should be a visible button below: +
+ +
+ + diff --git a/layout/reftests/bugs/1209994-3-ref.html b/layout/reftests/bugs/1209994-3-ref.html new file mode 100644 index 00000000000..bd219e03ae5 --- /dev/null +++ b/layout/reftests/bugs/1209994-3-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible table below: +
+ This is a table
+
+ diff --git a/layout/reftests/bugs/1209994-3.html b/layout/reftests/bugs/1209994-3.html new file mode 100644 index 00000000000..94df02fabb1 --- /dev/null +++ b/layout/reftests/bugs/1209994-3.html @@ -0,0 +1,21 @@ + + +There should be a visible table below: +
+ This is a table
+
+ + diff --git a/layout/reftests/bugs/1209994-4-ref.html b/layout/reftests/bugs/1209994-4-ref.html new file mode 100644 index 00000000000..2f15740fc16 --- /dev/null +++ b/layout/reftests/bugs/1209994-4-ref.html @@ -0,0 +1,17 @@ + + +There should be a visible select below: +
+ +
+ diff --git a/layout/reftests/bugs/1209994-4.html b/layout/reftests/bugs/1209994-4.html new file mode 100644 index 00000000000..95e2d062fac --- /dev/null +++ b/layout/reftests/bugs/1209994-4.html @@ -0,0 +1,21 @@ + + +There should be a visible select below: +
+ +
+ + diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index 5297adbbaf6..ec093885652 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1937,3 +1937,7 @@ fuzzy(1,74) fuzzy-if(gtkWidget,6,79) == 1174332-1.html 1174332-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 diff --git a/layout/reftests/css-display/display-contents-acid.html b/layout/reftests/css-display/display-contents-acid.html index 4302730d14b..bdd0d08eee8 100644 --- a/layout/reftests/css-display/display-contents-acid.html +++ b/layout/reftests/css-display/display-contents-acid.html @@ -32,7 +32,7 @@ .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; } diff --git a/layout/reftests/css-grid/grid-abspos-items-001-ref.html b/layout/reftests/css-grid/grid-abspos-items-001-ref.html index 93f8c75939e..a0361344501 100644 --- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html @@ -30,6 +30,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; position: absolute; left: 13px; top: 31px; height: 12px; width: 44px; + background: blue; } .abs { @@ -47,7 +48,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 112px; height: 82px; } .d { - left: 1px; top: 27px; + left: 1px; top: 20px; width: 5px; height: 1px; } .e { @@ -82,64 +83,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
@@ -162,17 +163,17 @@ span {
-a -b -c -d +a +b +c +d
- +
- +
diff --git a/layout/reftests/css-grid/grid-abspos-items-001.html b/layout/reftests/css-grid/grid-abspos-items-001.html index f80b79b15b4..97ac1f77b2d 100644 --- a/layout/reftests/css-grid/grid-abspos-items-001.html +++ b/layout/reftests/css-grid/grid-abspos-items-001.html @@ -29,6 +29,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; .a { grid-column: 1 / 3; grid-row: 3 / 5; + background: blue; } .abs { @@ -85,64 +86,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a +
b
-a + c
-a + d
-a +
e
-a + f
-a + g
diff --git a/layout/reftests/css-grid/grid-abspos-items-002-ref.html b/layout/reftests/css-grid/grid-abspos-items-002-ref.html index a581c47a4b1..9be7f8adf5f 100644 --- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html @@ -31,6 +31,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; position: absolute; left: 13px; top: 31px; height: 12px; width: 44px; + background: blue; } .abs { @@ -48,7 +49,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 112px; height: 82px; } .d { - left: 1px; top: 27px; + left: 1px; top: 20px; width: 5px; height: 1px; } .e { @@ -83,64 +84,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
@@ -163,10 +164,10 @@ span {
-a -b -c -d +a +b +c +d
diff --git a/layout/reftests/css-grid/grid-abspos-items-002.html b/layout/reftests/css-grid/grid-abspos-items-002.html index 015349a2363..a045ab0be10 100644 --- a/layout/reftests/css-grid/grid-abspos-items-002.html +++ b/layout/reftests/css-grid/grid-abspos-items-002.html @@ -29,6 +29,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; .a { grid-column: 1 / 3; grid-row: 3 / 5; + background: blue; } .abs { @@ -85,64 +86,64 @@ span {
-a + b
-a + c
-a + d
-a + e
-a + f
-a + g
-a +
b
-a + c
-a + d
-a +
e
-a + f
-a + g
diff --git a/layout/reftests/css-grid/grid-abspos-items-011-ref.html b/layout/reftests/css-grid/grid-abspos-items-011-ref.html index 92e540563e2..55ddb6da8b2 100644 --- a/layout/reftests/css-grid/grid-abspos-items-011-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-011-ref.html @@ -6,7 +6,7 @@ CSS Grid Test: abs pos areas in empty grid - + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-align-content-001.html b/layout/reftests/css-grid/grid-align-content-001.html new file mode 100644 index 00000000000..d1b12c72211 --- /dev/null +++ b/layout/reftests/css-grid/grid-align-content-001.html @@ -0,0 +1,90 @@ + + + + + CSS Grid Test: align-content + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html new file mode 100644 index 00000000000..58aba6fb5eb --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html @@ -0,0 +1,111 @@ + + + + + Reference: Testing 'auto' min-sizing with definite min-width/height + + + + + +
horizontal container, horizontal item
+
+
+
+
+
horizontal container, vertical item
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html new file mode 100644 index 00000000000..38a577195c2 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html @@ -0,0 +1,81 @@ + + + + + CSS Grid Test: 'auto' min-sizing with definite min-width/height + + + + + + + +
horizontal container, horizontal item
+
+
+
+
+
horizontal container, vertical item
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html new file mode 100644 index 00000000000..9f527f8157e --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001-ref.html @@ -0,0 +1,154 @@ + + + + + Reference: auto min-sizing with intrinsic min-width + + + + + + +
+ +
+
+ +
+
+ +
+ + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html new file mode 100644 index 00000000000..095cc1b5b04 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-001.html @@ -0,0 +1,148 @@ + + + + + CSS Grid Test: auto min-sizing with intrinsic min-width + + + + + + + + +
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html new file mode 100644 index 00000000000..ca7244a20df --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002-ref.html @@ -0,0 +1,155 @@ + + + + + Reference: 'auto' min-sizing with intrinsic min-width and overflow:hidden + + + + + + +
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html new file mode 100644 index 00000000000..c15afec9441 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-002.html @@ -0,0 +1,150 @@ + + + + + CSS Grid Test: 'auto' min-sizing with intrinsic min-width and overflow:hidden + + + + + + + + +
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html new file mode 100644 index 00000000000..cb7ca9ea5a3 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003-ref.html @@ -0,0 +1,66 @@ + + + + +Reference: min-width|min-height:auto + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyWideButIHaveADefiniteWidthSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'height':
+ +
+ a + IAmReallyTall + c + d +
+ +The border shouldn't shrink-wrap the wide text below, due to definite "height" values: +
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html new file mode 100644 index 00000000000..63cd48fff99 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-003.html @@ -0,0 +1,66 @@ + + + + +CSS Grid Test: min-width|min-height:auto + + + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyWideButIHaveADefiniteWidthSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'height':
+ +
+ a + IAmReallyTall + c + d +
+ +The border shouldn't shrink-wrap the wide text below, due to definite "height" values: +
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html new file mode 100644 index 00000000000..ddd050a7590 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004-ref.html @@ -0,0 +1,75 @@ + + + + +CSS Grid Test: min-width|min-height:auto w. vertical writing-mode + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "height" values:
+
+ a + IAmReallyWideButIHaveADefiniteHeightSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'width':
+ +
+ a + IAmReallyTall + c + d +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html new file mode 100644 index 00000000000..35874df27f9 --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-intrinsic-004.html @@ -0,0 +1,75 @@ + + + + +CSS Grid Test: min-width|min-height:auto w. vertical writing-mode + + + + + + + +
+ a + IAmReallyWideAndTheBorderShouldSurroundMe +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "height" values:
+
+ a + IAmReallyWideButIHaveADefiniteHeightSoIOverflow + c + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee + SameHereeeeeeeeeeeeeeeeeeeeeeeeeeee +
+ +
Now the same tests for 'width':
+ +
+ a + IAmReallyTall + c + d +
+ +
The border shouldn't shrink-wrap the wide text below, due to definite "width" values:
+
+ a + IAmReallyTall + c + SameHere + SameHere +
+ + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html new file mode 100644 index 00000000000..10f0f79f39e --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001-ref.html @@ -0,0 +1,136 @@ + + + + + Reference: Testing 'auto' min-sizing with percentage sizes + + + + + + + + +
no border/padding/margin'border-left:20px''padding-left:10%'
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html new file mode 100644 index 00000000000..6854ad71f2c --- /dev/null +++ b/layout/reftests/css-grid/grid-auto-min-sizing-percent-001.html @@ -0,0 +1,109 @@ + + + + + CSS Grid Test: Testing 'auto' min-sizing with percentage sizes + + + + + + + + + + +
no border/padding/margin'border-left:20px''padding-left:10%'
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-001-ref.html b/layout/reftests/css-grid/grid-item-align-001-ref.html new file mode 100644 index 00000000000..359baacd403 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-001-ref.html @@ -0,0 +1,190 @@ + + + + + Reference: align-self (part 1 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-001.html b/layout/reftests/css-grid/grid-item-align-001.html new file mode 100644 index 00000000000..da5b0b80037 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-001.html @@ -0,0 +1,107 @@ + + + + + CSS Grid Test: align-self (part 1 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-002-ref.html b/layout/reftests/css-grid/grid-item-align-002-ref.html new file mode 100644 index 00000000000..07cb0e11406 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-002-ref.html @@ -0,0 +1,127 @@ + + + + + Reference: align-self (part 2 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-002.html b/layout/reftests/css-grid/grid-item-align-002.html new file mode 100644 index 00000000000..c6282ea6919 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-002.html @@ -0,0 +1,108 @@ + + + + + CSS Grid Test: align-self (part 2 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-003-ref.html b/layout/reftests/css-grid/grid-item-align-003-ref.html new file mode 100644 index 00000000000..a8524d2fa5c --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-003-ref.html @@ -0,0 +1,118 @@ + + + + + Reference: align-self with overflow + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-align-003.html b/layout/reftests/css-grid/grid-item-align-003.html new file mode 100644 index 00000000000..e38324724a3 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-align-003.html @@ -0,0 +1,128 @@ + + + + + CSS Grid Test: align-self with overflow + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-001-ref.html b/layout/reftests/css-grid/grid-item-justify-001-ref.html new file mode 100644 index 00000000000..3af8fba42de --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-001-ref.html @@ -0,0 +1,121 @@ + + + + + CSS Grid Test: justify-self (part 1 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-001.html b/layout/reftests/css-grid/grid-item-justify-001.html new file mode 100644 index 00000000000..97cfa855fe7 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-001.html @@ -0,0 +1,108 @@ + + + + + CSS Grid Test: justify-self (part 1 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-002-ref.html b/layout/reftests/css-grid/grid-item-justify-002-ref.html new file mode 100644 index 00000000000..2d0f1e75da6 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-002-ref.html @@ -0,0 +1,120 @@ + + + + + CSS Grid Test: justify-self (part 2 of 2) + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-justify-002.html b/layout/reftests/css-grid/grid-item-justify-002.html new file mode 100644 index 00000000000..6512ddded04 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-justify-002.html @@ -0,0 +1,108 @@ + + + + + CSS Grid Test: justify-self (part 2 of 2) + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html b/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html new file mode 100644 index 00000000000..3a7f2d6bb0e --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html @@ -0,0 +1,94 @@ + + + + Reference 001 + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-001.html b/layout/reftests/css-grid/grid-item-sizing-percent-001.html new file mode 100644 index 00000000000..cc5948968ca --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-percent-001.html @@ -0,0 +1,96 @@ + + + + CSS Test: Testing grid item percent sizes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-sizing-px-001.html b/layout/reftests/css-grid/grid-item-sizing-px-001.html new file mode 100644 index 00000000000..a004365fc2c --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-px-001.html @@ -0,0 +1,94 @@ + + + + CSS Test: Testing grid item 'px' sizes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-stretch-001-ref.html b/layout/reftests/css-grid/grid-item-stretch-001-ref.html new file mode 100644 index 00000000000..3f53b119d8f --- /dev/null +++ b/layout/reftests/css-grid/grid-item-stretch-001-ref.html @@ -0,0 +1,109 @@ + + + + + Reference: align-items:stretch / justify-items:stretch + + + + +
+There should be no red areas.
+All grey areas should have a black dot in each corner.
+
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-item-stretch-001.html b/layout/reftests/css-grid/grid-item-stretch-001.html new file mode 100644 index 00000000000..cf5cc0fd205 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-stretch-001.html @@ -0,0 +1,117 @@ + + + + + CSS Grid Test: align-items:stretch / justify-items:stretch + + + + + + + +
+There should be no red areas.
+All grey areas should have a black dot in each corner.
+
+ + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-001-ref.html b/layout/reftests/css-grid/grid-justify-content-001-ref.html new file mode 100644 index 00000000000..a6b86f136e1 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-001-ref.html @@ -0,0 +1,92 @@ + + + + + CSS Grid Test: justify-content + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-001.html b/layout/reftests/css-grid/grid-justify-content-001.html new file mode 100644 index 00000000000..ab231c0fe63 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-001.html @@ -0,0 +1,90 @@ + + + + + CSS Grid Test: justify-content + + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-002-ref.html b/layout/reftests/css-grid/grid-justify-content-002-ref.html new file mode 100644 index 00000000000..6f976a11369 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-002-ref.html @@ -0,0 +1,62 @@ + + + + + CSS Grid Test: Testing track distribution rounding errors + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-002.html b/layout/reftests/css-grid/grid-justify-content-002.html new file mode 100644 index 00000000000..73220d041c0 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-002.html @@ -0,0 +1,67 @@ + + + + + CSS Grid Test: Testing track distribution rounding errors + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-003-ref.html b/layout/reftests/css-grid/grid-justify-content-003-ref.html new file mode 100644 index 00000000000..e22c4c9dd67 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-003-ref.html @@ -0,0 +1,115 @@ + + + + + Reference: Testing track fallback values + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-justify-content-003.html b/layout/reftests/css-grid/grid-justify-content-003.html new file mode 100644 index 00000000000..66fe0100369 --- /dev/null +++ b/layout/reftests/css-grid/grid-justify-content-003.html @@ -0,0 +1,96 @@ + + + + + CSS Grid Test: Testing 'justify-content' fallback values + + + + + + + + + + + diff --git a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html index ae720880b79..c01334985b7 100644 --- a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html @@ -45,7 +45,7 @@ body,html { color:black; background:white; font-size:16px; padding:0; margin:0; width: 51px; height: 2px; } .d { - left: 1px; top: 18px; + left: 1px; top: 11px; width: 212px; height: 1px; } .e { @@ -98,7 +98,7 @@ span {
-d +d
diff --git a/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html index 6b1c2db1c37..6d97fbb3706 100644 --- a/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-auto-implicit-001-ref.html @@ -12,7 +12,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; .grid { border: 1px solid blue; - width:300px; + width:270px; } .a { background:lime; } @@ -50,6 +50,7 @@ span {
ceab
+
cfab
@@ -74,24 +75,27 @@ span {
ggab
+ +
+
- -b
- + +b
+
- -b
- + + +b
- + b
- -b
- + +b
+
diff --git a/layout/reftests/css-grid/grid-placement-auto-implicit-001.html b/layout/reftests/css-grid/grid-placement-auto-implicit-001.html index 75622c2270f..c14bf06cc5f 100644 --- a/layout/reftests/css-grid/grid-placement-auto-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-auto-implicit-001.html @@ -20,7 +20,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; grid-auto-columns: 3px; grid-auto-rows: 20px; border: 1px solid blue; - width: 300px; + width: 270px; } .a { grid-area: 1 / 2; background:lime; } @@ -35,6 +35,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; span { border: 1px solid; line-height: 18px; + min-width: 0; } @@ -76,24 +77,27 @@ span {
abgg
+ +
+
- + b
- + b
- + b
- + b
diff --git a/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html index ad860c60289..a9684cfd1df 100644 --- a/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-definite-implicit-001-ref.html @@ -75,7 +75,7 @@ span { c
-b +
diff --git a/layout/reftests/css-grid/grid-placement-definite-implicit-001.html b/layout/reftests/css-grid/grid-placement-definite-implicit-001.html index 63b273fc398..d19cbdff81e 100644 --- a/layout/reftests/css-grid/grid-placement-definite-implicit-001.html +++ b/layout/reftests/css-grid/grid-placement-definite-implicit-001.html @@ -30,6 +30,7 @@ body,html { color:black; background:white; font-size:12px; padding:0; margin:0; span { border: 1px solid; line-height: 18px; + min-width: 0; } @@ -79,7 +80,7 @@ span { c
-b +
diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 0864cfbec3d..184bf3b1f3c 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -42,6 +42,8 @@ const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; const UNKNOWN_XPCOM_ABI = "unknownABI"; +const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; + const UPDATE_REQUEST_VERSION = 2; const CATEGORY_UPDATE_PARAMS = "extension-update-params"; @@ -663,6 +665,7 @@ var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; var gUpdateEnabled = true; var gAutoUpdateDefault = true; var gHotfixID = null; +var gWebExtensionsMinPlatformVersion = null; var gShutdownBarrier = null; var gRepoShutdownState = ""; var gShutdownInProgress = false; @@ -947,6 +950,11 @@ var AddonManagerInternal = { } catch (e) {} Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); + try { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + } catch (e) {} + Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); + let defaultProvidersEnabled = true; try { defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); @@ -1377,6 +1385,10 @@ var AddonManagerInternal = { } break; } + case PREF_MIN_WEBEXT_PLATFORM_VERSION: { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + break; + } } }, @@ -2894,6 +2906,10 @@ this.AddonManagerPrivate = { safeCall(listener.onUpdateFinished.bind(listener), addon); } }, + + get webExtensionsMinPlatformVersion() { + return gWebExtensionsMinPlatformVersion; + }, }; /** diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index f6becf98fb4..a567eb678de 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -31,6 +31,8 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", + "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm"); @@ -217,6 +219,48 @@ RDFSerializer.prototype = { } } +/** + * Sanitizes the update URL in an update item, as returned by + * parseRDFManifest and parseJSONManifest. Ensures that: + * + * - The URL is secure, or secured by a strong enough hash. + * - The security principal of the update manifest has permission to + * load the URL. + * + * @param aUpdate + * The update item to sanitize. + * @param aRequest + * The XMLHttpRequest used to load the manifest. + * @param aHashPattern + * The regular expression used to validate the update hash. + * @param aHashString + * The human-readable string specifying which hash functions + * are accepted. + */ +function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) { + if (aUpdate.updateURL) { + let scriptSecurity = Services.scriptSecurityManager; + let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel); + try { + // This logs an error on failure, so no need to log it a second time + scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL, + scriptSecurity.DISALLOW_SCRIPT); + } catch (e) { + delete aUpdate.updateURL; + return; + } + + if (AddonManager.checkUpdateSecurity && + !aUpdate.updateURL.startsWith("https:") && + !aHashPattern.test(aUpdate.updateHash)) { + logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` + + `by a strong enough hash (needs to be ${aHashString}).`); + delete aUpdate.updateURL; + delete aUpdate.updateHash; + } + } +} + /** * Parses an RDF style update manifest into an array of update objects. * @@ -226,10 +270,17 @@ RDFSerializer.prototype = { * An optional update key for the add-on * @param aRequest * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a bare XML DOM document * @return an array of update objects * @throws if the update manifest is invalid in any way */ -function parseRDFManifest(aId, aUpdateKey, aRequest) { +function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) { + throw Components.Exception("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); + return; + } + function EM_R(aProp) { return gRDF.GetResource(PREFIX_NS_EM + aProp); } @@ -366,20 +417,136 @@ function parseRDFManifest(aId, aUpdateKey, aRequest) { targetApplications: [appEntry] }; - if (result.updateURL && AddonManager.checkUpdateSecurity && - result.updateURL.substring(0, 6) != "https:" && - (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { - logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + - " by a strong enough hash (needs to be sha1 or stronger)."); - delete result.updateURL; - delete result.updateHash; - } + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger"); + results.push(result); } } return results; } +/** + * Parses an JSON update manifest into an array of update objects. + * + * @param aId + * The ID of the add-on being checked for updates + * @param aUpdateKey + * An optional update key for the add-on + * @param aRequest + * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a JSON object tree + * @return an array of update objects + * @throws if the update manifest is invalid in any way + */ +function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aUpdateKey) + throw Components.Exception("Update keys are not supported for JSON update manifests"); + + let TYPE_CHECK = { + "array": val => Array.isArray(val), + "object": val => val && typeof val == "object" && !Array.isArray(val), + }; + + function getProperty(aObj, aProperty, aType, aDefault = undefined) { + if (!(aProperty in aObj)) + return aDefault; + + let value = aObj[aProperty]; + + let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType; + if (!matchesType) + throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`); + + return value; + } + + function getRequiredProperty(aObj, aProperty, aType) { + let value = getProperty(aObj, aProperty, aType); + if (value === undefined) + throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`); + return value; + } + + let manifest = aManifestData; + + if (!TYPE_CHECK["object"](manifest)) + throw Components.Exception("Root element of update manifest must be a JSON object literal"); + + // The set of add-ons this manifest has updates for + let addons = getRequiredProperty(manifest, "addons", "object"); + + // The entry for this particular add-on + let addon = getProperty(addons, aId, "object"); + + // A missing entry doesn't count as a failure, just as no avialable update + // information + if (!addon) { + logger.warn("Update manifest did not contain an entry for " + aId); + return []; + } + + // The list of available updates + let updates = getProperty(addon, "updates", "array", []); + + let results = []; + + for (let update of updates) { + let version = getRequiredProperty(update, "version", "string"); + + logger.debug(`Found an update entry for ${aId} version ${version}`); + + let applications = getProperty(update, "applications", "object", + { gecko: {} }); + + // "gecko" is currently the only supported application entry. If + // it's missing, skip this update. + if (!("gecko" in applications)) + continue; + + let app = getProperty(applications, "gecko", "object"); + + let appEntry = { + id: TOOLKIT_ID, + minVersion: getProperty(app, "strict_min_version", "string", + AddonManagerPrivate.webExtensionsMinPlatformVersion), + maxVersion: "*", + }; + + let result = { + id: aId, + version: version, + multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true), + updateURL: getProperty(update, "update_link", "string"), + updateHash: getProperty(update, "update_hash", "string"), + updateInfoURL: getProperty(update, "update_info_url", "string"), + strictCompatibility: false, + targetApplications: [appEntry], + }; + + if ("strict_max_version" in app) { + if ("advisory_max_version" in app) { + logger.warn("Ignoring 'advisory_max_version' update manifest property for " + + aId + " property since 'strict_max_version' also present"); + } + + appEntry.maxVersion = getProperty(app, "strict_max_version", "string"); + result.strictCompatibility = appEntry.maxVersion != "*"; + } else if ("advisory_max_version" in app) { + appEntry.maxVersion = getProperty(app, "advisory_max_version", "string"); + } + + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512"); + + results.push(result); + } + return results; +} + /** * Starts downloading an update manifest and then passes it to an appropriate * parser to convert to an array of update objects @@ -415,7 +582,7 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // Prevent the request from writing to cache. this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - this.request.overrideMimeType("text/xml"); + this.request.overrideMimeType("text/plain"); this.request.setRequestHeader("Moz-XPI-Update", "1", true); this.request.timeout = TIMEOUT; var self = this; @@ -474,41 +641,50 @@ UpdateParser.prototype = { return; } - let xml = request.responseXML; - if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { - logger.warn("Update manifest was not valid XML"); + // Detect the manifest type by first attempting to parse it as + // JSON, and falling back to parsing it as XML if that fails. + let parser; + try { + try { + let json = JSON.parse(request.responseText); + + parser = () => parseJSONManifest(this.id, this.updateKey, request, json); + } catch (e if e instanceof SyntaxError) { + let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); + let xml = domParser.parseFromString(request.responseText, "text/xml"); + + if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) + throw new Error("Update manifest was not valid XML or JSON"); + + parser = () => parseRDFManifest(this.id, this.updateKey, request, xml); + } + } catch (e) { + logger.warn("onUpdateCheckComplete failed to determine manifest type"); + this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + return; + } + + let results; + try { + results = parser(); + } + catch (e) { + logger.warn("onUpdateCheckComplete failed to parse update manifest", e); this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); return; } - // We currently only know about RDF update manifests - if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { - let results = null; - + if ("onUpdateCheckComplete" in this.observer) { try { - results = parseRDFManifest(this.id, this.updateKey, request); + this.observer.onUpdateCheckComplete(results); } catch (e) { - logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e); - this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); - return; + logger.warn("onUpdateCheckComplete notification failed", e); } - if ("onUpdateCheckComplete" in this.observer) { - try { - this.observer.onUpdateCheckComplete(results); - } - catch (e) { - logger.warn("onUpdateCheckComplete notification failed", e); - } - } - else { - logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); - } - return; } - - logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); - this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + else { + logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); + } }, /** diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 24efbd7deae..eb978d7ab70 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -916,7 +916,7 @@ function loadManifestFromWebManifest(aStream) { addon.targetApplications = [{ id: TOOLKIT_ID, - minVersion: "42a1", + minVersion: AddonManagerPrivate.webExtensionsMinPlatformVersion, maxVersion: "*", }]; diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json new file mode 100644 index 00000000000..027a9b2333f --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json @@ -0,0 +1,215 @@ +{ + "addons": { + "addon1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + }, + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_min_version": "2" + } + } + }, + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update.xpi", + "update_info_url": "http://example.com/updateInfo.xhtml", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon3@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "3", + "advisory_max_version": "3" + } + } + } + ] + }, + + "addon4@tests.mozilla.org": { + "updates": [ + { + "version": "5.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "0" + } + } + } + ] + }, + + "addon7@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon8@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update8.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon9@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + }, + { + "_comment_": "Incompatible when strict compatibility is enabled", + "version": "3.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_3.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Incompatible due to compatibility override", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_4.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Addon for future version of app", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_5.xpi", + "applications": { + "gecko": { + "strict_min_version": "5", + "advisory_max_version": "6" + } + } + } + ] + }, + + "addon10@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "http://localhost:%PORT%/addons/test_update10.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.4" + } + } + } + ] + }, + + "addon11@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update11.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + }, + + "addon12@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update12.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json new file mode 100644 index 00000000000..811e50158ea --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json @@ -0,0 +1,327 @@ +{ + "addons": { + "updatecheck1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + }, + { + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "4.0", + "update_link": "https://localhost:4444/addons/test4.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure and there ", + "_comment_": "is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_7@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure ", + "_comment_": "and there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_8@tests.mozilla.org": { + "_comment_": "The updateLink will be ignored since it is not secure and ", + "_comment_": "there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_9@tests.mozilla.org": { + "_comment_": "The updateLink will used since there is an updateHash to verify it.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha256:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_10@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_11@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_12@tests.mozilla.org": { + "_comment_": "The updateLink will not be used since the updateHash ", + "_comment_": "verifying it is not strong enough.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_13@tests.mozilla.org": { + "_comment_": "An update with a weak hash. The updateLink will used since it is ", + "_comment_": "a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "_comment_": "There should be no information present for test_bug378216_14", + + "test_bug378216_15@tests.mozilla.org": { + "_comment_": "Invalid update JSON", + + "updates": "foo" + }, + + "ignore-compat@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-override@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "_comment_": "Has compatibility override, but it doesn't match this app version", + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "_comment_": "Has compatibility override, so is incompaible", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-strict-optin@tests.mozilla.org": { + "_comment_": "Opt-in to strict compatibility checking", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "_comment_": "strictCompatibility: true", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf index 93c82886a65..c5d97ada0d5 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf @@ -236,7 +236,7 @@ A90eF5zy - diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index a5e6ba3d1a3..27bca41f0f2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1542,7 +1542,7 @@ if ("nsIWindowsRegKey" in AM_Ci) { * This is a mock nsIWindowsRegistry implementation. It only implements the * methods that the extension manager requires. */ - function MockWindowsRegKey() { + var MockWindowsRegKey = function MockWindowsRegKey() { } MockWindowsRegKey.prototype = { @@ -1723,6 +1723,30 @@ do_register_cleanup(function addon_cleanup() { } catch (e) {} }); +/** + * Creates a new HttpServer for testing, and begins listening on the + * specified port. Automatically shuts down the server when the test + * unit ends. + * + * @param port + * The port to listen on. If omitted, listen on a random + * port. The latter is the preferred behavior. + * + * @return HttpServer + */ +function createHttpServer(port = -1) { + let server = new HttpServer(); + server.start(port); + + do_register_cleanup(() => { + return new Promise(resolve => { + server.stop(resolve); + }); + }); + + return server; +} + /** * Handler function that responds with the interpolated * static file associated to the URL specified by request.path. @@ -1896,19 +1920,89 @@ function promiseAddonByID(aId) { */ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) { return new Promise((resolve, reject) => { + let result = {}; addon.findUpdates({ - install: null, - - onUpdateAvailable: function(addon, install) { - this.install = install; + onNoCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = false; }, - onUpdateFinished: function(addon, error) { - if (error == AddonManager.UPDATE_STATUS_NO_ERROR) - resolve(this.install); - else - reject(error); + onCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = true; + }, + + onNoUpdateAvailable: function(addon2) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = false; + }, + + onUpdateAvailable: function(addon2, install) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = install; + }, + + onUpdateFinished: function(addon2, error) { + equal(addon, addon2); + if (error == AddonManager.UPDATE_STATUS_NO_ERROR) { + resolve(result); + } else { + result.error = error; + reject(result); + } } }, reason); }); } + +/** + * Monitors console output for the duration of a task, and returns a promise + * which resolves to a tuple containing a list of all console messages + * generated during the task's execution, and the result of the task itself. + * + * @param {function} aTask + * The task to run while monitoring console output. May be + * either a generator function, per Task.jsm, or an ordinary + * function which returns promose. + * @return {Promise<[Array, *]>} + */ +var promiseConsoleOutput = Task.async(function*(aTask) { + const DONE = "=== xpcshell test console listener done ==="; + + let listener, messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else { + msg instanceof Components.interfaces.nsIScriptError; + messages.push(msg); + } + } + }); + + Services.console.registerListener(listener); + try { + let result = yield aTask(); + + Services.console.logStringMessage(DONE); + yield awaitListener; + + return { messages, result }; + } + finally { + Services.console.unregisterListener(listener); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index b2c0f59013e..2ba21a8a213 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -505,9 +505,8 @@ add_task(function* setup() { yield promiseInstallAllFiles(ADDON_FILES); yield promiseRestartManager(); - gServer = new HttpServer(); + gServer = createHttpServer(PORT); gServer.registerDirectory("/data/", do_get_file("data")); - gServer.start(PORT); }); // Tests AddonRepository.cacheEnabled @@ -704,7 +703,3 @@ add_task(function* run_test_17() { let aAddons = yield promiseAddonsByIDs(ADDON_IDS); check_results(aAddons, WITH_EXTENSION_CACHE); }); - -add_task(function* end_test() { - yield new Promise((resolve, reject) => gServer.stop(resolve)); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 989c3e4ceea..7113f7cfd54 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -41,8 +41,7 @@ Cu.import("resource://testing-common/MockRegistrar.jsm"); Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false) Cu.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; // register static files with server and interpolate port numbers in them @@ -1305,9 +1304,3 @@ add_task(function* run_local_install_test() { check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); }); - -add_task(function* shutdown_httpserver() { - yield new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index 7c274d90aef..67b50d16190 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -296,10 +296,9 @@ add_task(function* init() { }, profileDir); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(4444); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(4444); startupManager(); @@ -464,13 +463,3 @@ add_task(function* run_test_6() { "override1x2-1x3@tests.mozilla.org"]); check_state_v1_2(addons); }); - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js index 70de3b426d2..c859c474c71 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js @@ -16,65 +16,47 @@ function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_missing.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); + run_next_test(); } // Verify that an update check returns the correct errors. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); +add_task(function* () { + for (let manifestType of ["rdf", "json"]) { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: `http://localhost:${gPort}/data/test_missing.${manifestType}`, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + bootstrap: "true", + }, profileDir); - let sawCompat = false; - let sawUpdate = false; - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - sawCompat = true; - }, + yield promiseRestartManager(); - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen a compatibility update"); - }, + let addon = yield promiseAddonByID("addon1@tests.mozilla.org"); - onNoUpdateAvailable: function(addon) { - sawUpdate = true; - }, + ok(addon); + ok(addon.updateURL.endsWith(manifestType)); + equal(addon.version, "1.0"); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an update"); - }, + // We're expecting an error, so resolve when the promise is rejected. + let update = yield promiseFindAddonUpdates(addon, AddonManager.UPDATE_WHEN_USER_REQUESTED) + .catch(Promise.resolve); - onUpdateFinished: function(addon, error) { - do_check_true(sawCompat); - do_check_true(sawUpdate); - do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); - end_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + ok(!update.compatibilityUpdate, "not expecting a compatibility update"); + ok(!update.updateAvailable, "not expecting a compatibility update"); + + equal(update.error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); + + addon.uninstall(); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js new file mode 100644 index 00000000000..2e8cc6a972c --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js @@ -0,0 +1,373 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +// This verifies that AddonUpdateChecker works correctly for JSON +// update manifests, particularly for behavior which does not +// cleanly overlap with RDF manifests. + +const TOOLKIT_ID = "toolkit@mozilla.org"; +const TOOLKIT_MINVERSION = "42.0a1"; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0a2", "42.0a2"); + +Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); +Components.utils.import("resource://testing-common/httpd.js"); + +let testserver = createHttpServer(); +gPort = testserver.identity.primaryPort; + +let gUpdateManifests = {}; + +function mapManifest(aPath, aManifestData) { + gUpdateManifests[aPath] = aManifestData; + testserver.registerPathHandler(aPath, serveManifest); +} + +function serveManifest(request, response) { + let manifest = gUpdateManifests[request.path]; + + response.setHeader("Content-Type", manifest.contentType, false); + response.write(manifest.data); +} + +const extensionsDir = gProfD.clone(); +extensionsDir.append("extensions"); + + +function checkUpdates(aData) { + // Registers JSON update manifest for it with the testing server, + // checks for updates, and yields the list of updates on + // success. + + let extension = aData.manifestExtension || "json"; + + let path = `/updates/${aData.id}.${extension}`; + let updateUrl = `http://localhost:${gPort}${path}` + + let addonData = {}; + if ("updates" in aData) + addonData.updates = aData.updates; + + let manifestJSON = { + "addons": { + [aData.id]: addonData + } + }; + + mapManifest(path.replace(/\?.*/, ""), + { data: JSON.stringify(manifestJSON), + contentType: aData.contentType || "application/json" }); + + + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aData.id, aData.updateKey, updateUrl, { + onUpdateCheckComplete: resolve, + + onUpdateCheckError: function(status) { + reject(new Error("Update check failed with status " + status)); + } + }); + }); +} + + +add_task(function* test_default_values() { + // Checks that the appropriate defaults are used for omitted values. + + startupManager(); + + let updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2" + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, TOOLKIT_MINVERSION); + equal(targetApp.maxVersion, "*"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, true, "multiprocess_compatible flag"); + equal(update.strictCompatibility, false, "inferred strictConpatibility flag"); + equal(update.updateURL, null, "updateURL"); + equal(update.updateHash, null, "updateHash"); + equal(update.updateInfoURL, null, "updateInfoURL"); + + // If there's no applications property, we default to using one + // containing "gecko". If there is an applications property, but + // it doesn't contain "gecko", the update is skipped. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + applications: { "foo": {} } + }] + }); + + equal(updates.length, 0); + + // Updates property is also optional. No updates, but also no error. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + }); + + equal(updates.length, 0); +}); + + +add_task(function* test_explicit_values() { + // Checks that the appropriate explicit values are used when + // provided. + + let updates = yield checkUpdates({ + id: "updatecheck-explicit@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + update_link: "https://example.com/foo.xpi", + update_hash: "sha256:0", + update_info_url: "https://example.com/update_info.html", + multiprocess_compatible: false, + applications: { + gecko: { + strict_min_version: "42.0a2.xpcshell", + strict_max_version: "43.xpcshell" + } + } + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, "42.0a2.xpcshell"); + equal(targetApp.maxVersion, "43.xpcshell"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, false, "multiprocess_compatible flag"); + equal(update.strictCompatibility, true, "inferred strictCompatibility flag"); + equal(update.updateURL, "https://example.com/foo.xpi", "updateURL"); + equal(update.updateHash, "sha256:0", "updateHash"); + equal(update.updateInfoURL, "https://example.com/update_info.html", "updateInfoURL"); +}); + + +add_task(function* test_secure_hashes() { + // Checks that only secure hash functions are accepted for + // non-secure update URLs. + + let hashFunctions = ["sha512", + "sha256", + "sha1", + "md5", + "md4", + "xxx"]; + + let updateItems = hashFunctions.map((hash, idx) => ({ + version: `0.${idx}`, + update_link: `http://localhost:${gPort}/updates/${idx}-${hash}.xpi`, + update_hash: `${hash}:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a`, + })); + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-hashes@tests.mozilla.org", + version: "0.1", + updates: updateItems + }); + }); + + equal(updates.length, hashFunctions.length); + + updates = updates.filter(update => update.updateHash || update.updateURL); + equal(updates.length, 2, "expected number of update hashes were accepted"); + + ok(updates[0].updateHash.startsWith("sha512:"), "sha512 hash is present"); + ok(updates[0].updateURL); + + ok(updates[1].updateHash.startsWith("sha256:"), "sha256 hash is present"); + ok(updates[1].updateURL); + + messages = messages.filter(msg => /Update link.*not secure.*strong enough hash \(needs to be sha256 or sha512\)/.test(msg.message)); + equal(messages.length, hashFunctions.length - 2, "insecure hashes generated the expected warning"); +}); + + +add_task(function* test_strict_compat() { + // Checks that strict compatibility is enabled for strict max + // versions other than "*", but not for advisory max versions. + // Also, ensure that strict max versions take precedence over + // advisory versions. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-strict@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + applications: { gecko: { strict_max_version: "*" } } }, + { version: "0.3", + applications: { gecko: { strict_max_version: "43" } } }, + { version: "0.4", + applications: { gecko: { advisory_max_version: "43" } } }, + { version: "0.5", + applications: { gecko: { advisory_max_version: "43", + strict_max_version: "44" } } }, + ] + }); + }); + + equal(updates.length, 4, "all update items accepted"); + + equal(updates[0].targetApplications[0].maxVersion, "*"); + equal(updates[0].strictCompatibility, false); + + equal(updates[1].targetApplications[0].maxVersion, "43"); + equal(updates[1].strictCompatibility, true); + + equal(updates[2].targetApplications[0].maxVersion, "43"); + equal(updates[2].strictCompatibility, false); + + equal(updates[3].targetApplications[0].maxVersion, "44"); + equal(updates[3].strictCompatibility, true); + + messages = messages.filter(msg => /Ignoring 'advisory_max_version'.*'strict_max_version' also present/.test(msg.message)); + equal(messages.length, 1, "mix of advisory_max_version and strict_max_version generated the expected warning"); +}); + + +add_task(function* test_update_url_security() { + // Checks that update links to privileged URLs are not accepted. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-security@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + update_link: "chrome://browser/content/browser.xul", + update_hash: "sha256:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + { version: "0.3", + update_link: "http://example.com/update.xpi", + update_hash: "sha256:18ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + ] + }); + }); + + equal(updates.length, 2, "both updates were processed"); + equal(updates[0].updateURL, null, "privileged update URL was removed"); + equal(updates[1].updateURL, "http://example.com/update.xpi", "safe update URL was accepted"); + + messages = messages.filter(msg => /http:\/\/localhost.*\/updates\/.*may not load or link to chrome:/.test(msg.message)); + equal(messages.length, 1, "privileged upate URL generated the expected console message"); +}); + + +add_task(function* test_no_update_key() { + // Checks that updates fail when an update key has been specified. + + let { messages } = yield promiseConsoleOutput(function* () { + yield Assert.rejects( + checkUpdates({ + id: "updatecheck-updatekey@tests.mozilla.org", + version: "0.1", + updateKey: "ayzzx=", + updates: [ + { version: "0.2" }, + { version: "0.3" }, + ] + }), + null, "updated expected to fail"); + }); + + messages = messages.filter(msg => /Update keys are not supported for JSON update manifests/.test(msg.message)); + equal(messages.length, 1, "got expected update-key-unsupported error"); +}); + + +add_task(function* test_type_detection() { + // Checks that JSON update manifests are detected correctly + // regardless of extension or MIME type. + + let tests = [ + { contentType: "application/json", + extension: "json", + valid: true }, + { contentType: "application/json", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "json", + valid: true }, + { contentType: "application/octet-stream", + extension: "json", + valid: true }, + { contentType: "text/plain", + extension: "json?foo=bar", + valid: true }, + { contentType: "text/plain", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "rdf", + valid: true }, + { contentType: "application/json", + extension: "rdf", + valid: true }, + { contentType: "text/xml", + extension: "json", + valid: true }, + { contentType: "application/rdf+xml", + extension: "json", + valid: true }, + ]; + + for (let [i, test] of tests.entries()) { + let { messages } = yield promiseConsoleOutput(function *() { + let id = `updatecheck-typedetection-${i}@tests.mozilla.org`; + let updates; + try { + updates = yield checkUpdates({ + id: id, + version: "0.1", + contentType: test.contentType, + manifestExtension: test.extension, + updates: [{ version: "0.2" }] + }); + } catch (e) { + ok(!test.valid, "update manifest correctly detected as RDF"); + return; + } + + ok(test.valid, "update manifest correctly detected as JSON"); + equal(updates.length, 1, "correct number of updates"); + equal(updates[0].id, id, "update is for correct extension"); + }); + + if (test.valid) { + // Make sure we don't get any XML parsing errors from the + // XMLHttpRequest machinery. + ok(!messages.some(msg => /not well-formed/.test(msg.message)), + "expect XMLHttpRequest not to attempt XML parsing"); + } + + messages = messages.filter(msg => /Update manifest was not valid XML/.test(msg.message)); + equal(messages.length, !test.valid, "expected number of XML parsing errors"); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js index 78d03e88cd2..962e0c8cc63 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js @@ -69,10 +69,9 @@ add_task(function* checkFirstMetadata() { Services.prefs.setBoolPref(PREF_EM_SHOW_MISMATCH_UI, true); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; const BASE_URL = "http://localhost:" + gPort; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; @@ -159,15 +158,3 @@ add_task(function* upgrade_young_pref_lastupdate() { yield promiseRestartManager("2"); do_check_false(WindowWatcher.expected); }); - - - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js index 8c7f491d145..392a9b7a247 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js @@ -16,8 +16,7 @@ const WORKING = "signed_bootstrap_1.xpi"; const ID = "test@tests.mozilla.org"; Components.utils.import("resource://testing-common/httpd.js"); -var gServer = new HttpServer(); -gServer.start(4444); +var gServer = createHttpServer(4444); // Creates an add-on with a broken signature by changing an existing file function createBrokenAddonModify(file) { @@ -137,7 +136,8 @@ function* test_update_broken(file, expectedError) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let install = yield promiseFindAddonUpdates(addon); + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); @@ -158,7 +158,8 @@ function* test_update_working(file, expectedSignedState) { serveUpdateRDF(file.leafName); let addon = yield promiseAddonByID(ID); - let install = yield promiseFindAddonUpdates(addon); + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; yield promiseCompleteAllInstalls([install]); do_check_eq(install.state, AddonManager.STATE_INSTALLED); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index f410382c2d2..07e8c37128c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -24,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -37,386 +37,1160 @@ profileDir.append("extensions"); var originalSyncGUID; function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - testserver.stop(do_test_finished); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); +for (let test of testParams) { + let { updateFile, appId } = test; - originalSyncGUID = a1.syncGUID; - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + + originalSyncGUID = a1.syncGUID; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + do_check_eq(originalSyncGUID, a1.syncGUID); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); - do_check_eq(originalSyncGUID, a1.syncGUID); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is not compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that a compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for, when + // strict compatibility checking is disabled. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + add_test(function run_test_16() { + restartManager(); + + restartManager(); + + let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_1_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { + do_check_neq(a1.syncGUID, null); + let oldGUID = a1.syncGUID; + + let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_2_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2.syncGUID, null); + do_check_eq(oldGUID, a2.syncGUID); + + a2.uninstall(); + run_next_test(); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "3.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Tests that compatibility updates are applied to addons when the updated + // compatibility data wouldn't match with strict compatibility enabled. + add_test(function run_test_18() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon10@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 10", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { + do_check_neq(a10, null); + + a10.findUpdates({ + onNoCompatibilityUpdateAvailable: function() { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + a10.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + onUpdateFinished: function() { + a11.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update succeeds when the update.rdf URN contains a type prefix + // different from the add-on type + let continue_test_20; + add_test(function run_test_20() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon12@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 12", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_20); - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); + AddonManagerPrivate.backgroundUpdateCheck(); }); -} -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); + let check_test_20; + continue_test_20 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} + prepare_test({ + "addon12@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_20)); + } -// Test that background update checks work -function run_test_6() { - restartManager(); + check_test_20 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); + restartManager(); + AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { + do_check_neq(a12, null); + do_check_eq(a12.version, "2.0"); + do_check_eq(a12.type, "extension"); + a12.uninstall(); - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); + do_execute_soon(() => { + restartManager(); + run_next_test() + }); + }); + } - AddonManagerInternal.backgroundUpdateCheck(); -} + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + for (let addon of addons) + addon.uninstall(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} + yield promiseRestartManager(); -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); + shutdownManager(); - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -484,7 +1258,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -501,13 +1275,13 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_test_7_cache(); + run_next_test(); }); } // Test that background update checks for lightweight themes do not use the cache // The update body from test 7 shouldn't be used since the cache should be bypassed. -function run_test_7_cache() { +add_test(function () { // XXX The lightweight theme manager strips non-https updateURLs so hack it // back in. let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes")); @@ -550,7 +1324,7 @@ function run_test_7_cache() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7_cache() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -571,740 +1345,6 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that a compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for, when -// strict compatibility checking is disabled. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -function run_test_16() { - restartManager(); - - restartManager(); - - let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_1_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { - do_check_neq(a1.syncGUID, null); - let oldGUID = a1.syncGUID; - - let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_2_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2.syncGUID, null); - do_check_eq(oldGUID, a2.syncGUID); - - a2.uninstall(); - do_execute_soon(run_test_17); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_17() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "3.0"); - }, - onDownloadFailed: function(aInstall) { - AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { - a9.uninstall(); - do_execute_soon(run_test_18); - }); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Tests that compatibility updates are applied to addons when the updated -// compatibility data wouldn't match with strict compatibility enabled. -function run_test_18() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon10@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 10", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { - do_check_neq(a10, null); - - a10.findUpdates({ - onNoCompatibilityUpdateAvailable: function() { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a10.uninstall(); - do_execute_soon(run_test_19); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_19() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a11.uninstall(); - do_execute_soon(run_test_20); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update succeeds when the update.rdf URN contains a type prefix -// different from the add-on type -function run_test_20() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon12@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 12", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_20); - - AddonManagerPrivate.backgroundUpdateCheck(); -} - -function continue_test_20(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - - prepare_test({ - "addon12@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_20)); -} - -function check_test_20(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { - do_check_neq(a12, null); - do_check_eq(a12.version, "2.0"); - do_check_eq(a12.type, "extension"); - a12.uninstall(); - - do_execute_soon(() => { - restartManager(); - end_test(); - }); + run_next_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js index 672594088fb..16fa3a4c18b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -9,12 +9,13 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -22,77 +23,86 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_1(); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_1() { - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); +for (let test of testParams) { + let { updateFile, appId } = test; - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "4.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_2); - } - }); + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + restartManager(); - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_2() { - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "4.0"); }, - - onNoUpdateAvailable: function() { - do_throw("Should have seen an available update"); - }, - - onUpdateFinished: function() { - end_test(); + onDownloadFailed: function(aInstall) { + run_next_test(); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/" + updateFile); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 3c56f9adb84..3fc16a0f7e1 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -9,7 +9,8 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. -Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); @@ -23,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -34,383 +35,1013 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; - testserver.stop(do_test_finished); -} +for (let test of testParams) { + let { updateFile, appId } = test; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_false(a2.isCompatible); + do_check_true(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_true(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_false(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled,incompatible"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_false(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_false(a4.isActive, "addon4 is active"); + do_check_false(a4.isCompatible, "addon4 is compatible"); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that no compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_false(a7.isCompatible); + do_check_true(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "2.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + for (let addon of addons) + addon.uninstall(); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + yield promiseRestartManager(); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); - }); -} - -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} - -// Test that background update checks work -function run_test_6() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); - - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} - -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -474,7 +1105,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -491,595 +1122,6 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled,incompatible"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_false(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_false(a4.isActive); - do_check_false(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that no compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_false(a7.isCompatible); - do_check_true(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_16() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "2.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_17); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_17() { - - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - do_execute_soon(end_test); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + run_next_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js index 9d251933aab..9a2f6a01dbf 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js @@ -7,52 +7,47 @@ Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); Components.utils.import("resource://testing-common/httpd.js"); -var testserver; -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +var testserver = createHttpServer(4444); +testserver.registerDirectory("/data/", do_get_file("data")); - // Create and configure the HTTP server. - testserver = new HttpServer(); - testserver.registerDirectory("/data/", do_get_file("data")); - testserver.start(4444); +function checkUpdates(aId, aUpdateKey, aUpdateFile) { + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aId, aUpdateKey, `http://localhost:4444/data/${aUpdateFile}`, { + onUpdateCheckComplete: resolve, - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); -} - -// Test that a basic update check returns the expected available updates -function run_test_1() { - AddonUpdateChecker.checkForUpdates("updatecheck1@tests.mozilla.org", null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - check_test_1(updates); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } + onUpdateCheckError: function(status) { + let error = new Error("Update check failed with status " + status); + error.status = status; + reject(error); + } + }); }); } -function check_test_1(updates) { - do_check_eq(updates.length, 5); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); - do_check_neq(update, null); - do_check_eq(update.version, 3); - update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); - do_check_neq(update, null); - do_check_eq(update.version, 2); - do_check_eq(update.targetApplications[0].minVersion, 1); - do_check_eq(update.targetApplications[0].maxVersion, 2); +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_2(); + run_next_test(); } +// Test that a basic update check returns the expected available updates +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("updatecheck1@tests.mozilla.org", null, file); + + equal(updates.length, 5); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); + notEqual(update, null); + equal(update.version, "3.0"); + update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); + notEqual(update, null); + equal(update.version, "2.0"); + equal(update.targetApplications[0].minVersion, "1"); + equal(update.targetApplications[0].maxVersion, "2"); + } +}); + /* * Tests that the security checks are applied correctly * @@ -73,240 +68,169 @@ var updateKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK426erD/H3XtsjvaB5+PJqbh "NyeP6i4LuUYjTURnn7Yw/IgzyIJ2oKsYa32RuxAyteqAWqPT/J63wBixIeCxmysf" + "awB/zH4KaPiY3vnrzQIDAQAB"; -function run_test_2() { - AddonUpdateChecker.checkForUpdates("test_bug378216_5@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_5@tests.mozilla.org", + updateKey, file); + throw "Expected the update check to fail"; + } catch (e) {} + } +}); - onUpdateCheckError: function(status) { - run_test_3(); +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_7@tests.mozilla.org", + updateKey, file); + + throw "Expected the update check to fail"; + } catch (e) {} + } +}); + +add_task(function* () { + // Make sure that the JSON manifest is rejected when an update key is + // required, but perform the remaining tests which aren't expected to fail + // because of the update key, without requiring one for the JSON variant. + + try { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + updateKey, "test_updatecheck.json"); + + throw "Expected the update check to fail"; + } catch(e) {} + + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + key, file); + equal(updates.length, 1); + ok(!("updateURL" in updates[0])); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_9@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_10@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_11@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_12@tests.mozilla.org", + key, file); + equal(updates.length, 1); + do_check_false("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_13@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("test_bug378216_14@tests.mozilla.org", + null, file); + equal(updates.length, 0); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_15@tests.mozilla.org", + null, file); + + throw "Update check should have failed"; + } catch (e) { + equal(e.status, AddonUpdateChecker.ERROR_PARSE_ERROR); } - }); -} + } +}); -function run_test_3() { - AddonUpdateChecker.checkForUpdates("test_bug378216_7@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("ignore-compat@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true); + notEqual(update, null); + equal(update.version, 2); + } +}); - onUpdateCheckError: function(status) { - run_test_4(); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-override@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let overrides = [{ + type: "incompatible", + minVersion: 1, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 0.1, + appMaxVersion: 0.2 + }, { + type: "incompatible", + minVersion: 2, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 1, + appMaxVersion: 2 + }]; + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false, overrides); + notEqual(update, null); + equal(update.version, 1); + } +}); -function run_test_4() { - AddonUpdateChecker.checkForUpdates("test_bug378216_8@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_5(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_5() { - AddonUpdateChecker.checkForUpdates("test_bug378216_9@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_6(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_6() { - AddonUpdateChecker.checkForUpdates("test_bug378216_10@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_7(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_7() { - AddonUpdateChecker.checkForUpdates("test_bug378216_11@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_8(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_8() { - AddonUpdateChecker.checkForUpdates("test_bug378216_12@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_9(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_9() { - AddonUpdateChecker.checkForUpdates("test_bug378216_13@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_10(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_10() { - AddonUpdateChecker.checkForUpdates("test_bug378216_14@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 0); - run_test_11(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_11() { - AddonUpdateChecker.checkForUpdates("test_bug378216_15@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Update check should have failed"); - }, - - onUpdateCheckError: function(status) { - do_check_eq(status, AddonUpdateChecker.ERROR_PARSE_ERROR); - run_test_12(); - } - }); -} - -function run_test_12() { - AddonUpdateChecker.checkForUpdates("ignore-compat@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true); - do_check_neq(update, null); - do_check_eq(update.version, 2); - run_test_13(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_13() { - AddonUpdateChecker.checkForUpdates("compat-override@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let overrides = [{ - type: "incompatible", - minVersion: 1, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 0.1, - appMaxVersion: 0.2 - }, { - type: "incompatible", - minVersion: 2, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 1, - appMaxVersion: 2 - }]; - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false, - overrides); - do_check_neq(update, null); - do_check_eq(update.version, 1); - run_test_14(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_14() { - AddonUpdateChecker.checkForUpdates("compat-strict-optin@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false); - do_check_eq(update, null); - end_test(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-strict-optin@tests.mozilla.org", + null, file); + equal(updates.length, 1); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false); + equal(update, null); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index f8b9972cf28..fdd2dd9ac35 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -279,6 +279,7 @@ skip-if = os == "android" # Bug 676992: test consistently hangs on Android skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. +[test_json_updatecheck.js] [test_updateid.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index fbee3f072ce..3dcf02f5dd4 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -210,7 +210,7 @@ checksum: upload: checksum $(PYTHON) -u $(MOZILLA_DIR)/build/upload.py --base-path $(DIST) \ - --package $(PACKAGE) \ + --package '$(PACKAGE)' \ --properties-file $(DIST)/mach_build_properties.json \ $(UPLOAD_FILES) \ $(CHECKSUM_FILES) diff --git a/webapprt/Makefile.in b/webapprt/Makefile.in index ac3998cd729..7c9d774ce80 100644 --- a/webapprt/Makefile.in +++ b/webapprt/Makefile.in @@ -2,14 +2,10 @@ # 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 config.mk explicitly so we can override FINAL_TARGET. -include $(topsrcdir)/config/config.mk - +# Include rules.mk explicitly so we can use FINAL_TARGET. Also, the dependencies +# for webapprt.ini need to be set after PP_TARGETS are expanded in rules.mk. include $(topsrcdir)/config/rules.mk -libs:: $(call mkdir_deps,$(FINAL_TARGET)) - $(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest 'resource webapprt ./') - MOZ_APP_BUILDID := $(shell cat $(DEPTH)/config/buildid) DEFINES += -DMOZ_APP_BUILDID=$(MOZ_APP_BUILDID) diff --git a/webapprt/jar.mn b/webapprt/jar.mn index fdf7fef0680..4bd129783c7 100644 --- a/webapprt/jar.mn +++ b/webapprt/jar.mn @@ -15,3 +15,7 @@ webapprt.jar: content/downloads/downloads.js (content/downloads/downloads.js) content/downloads/downloads.css (content/downloads/downloads.css) content/downloads/download.xml (content/downloads/download.xml) + +# Trick to put the resource line in dist/bin/webapprt/chrome.manifest +[.] chrome.jar: +% resource webapprt ./ diff --git a/widget/android/GeneratedJNINatives.h b/widget/android/GeneratedJNINatives.h index 5b7a37e4330..cc289ba10b9 100644 --- a/widget/android/GeneratedJNINatives.h +++ b/widget/android/GeneratedJNINatives.h @@ -69,18 +69,10 @@ public: mozilla::jni::NativeStub ::template Wrap<&Impl::OnImeAddCompositionRange>), - mozilla::jni::MakeNativeMethod( - mozilla::jni::NativeStub - ::template Wrap<&Impl::OnImeRemoveComposition>), - mozilla::jni::MakeNativeMethod( mozilla::jni::NativeStub ::template Wrap<&Impl::OnImeReplaceText>), - mozilla::jni::MakeNativeMethod( - mozilla::jni::NativeStub - ::template Wrap<&Impl::OnImeSetSelection>), - mozilla::jni::MakeNativeMethod( mozilla::jni::NativeStub ::template Wrap<&Impl::OnImeSynchronize>), diff --git a/widget/android/GeneratedJNIWrappers.cpp b/widget/android/GeneratedJNIWrappers.cpp index a969c117c32..2cf1cfaf4c9 100644 --- a/widget/android/GeneratedJNIWrappers.cpp +++ b/widget/android/GeneratedJNIWrappers.cpp @@ -769,15 +769,9 @@ constexpr char GeckoEditable::OnImeAcknowledgeFocus_t::signature[]; constexpr char GeckoEditable::OnImeAddCompositionRange_t::name[]; constexpr char GeckoEditable::OnImeAddCompositionRange_t::signature[]; -constexpr char GeckoEditable::OnImeRemoveComposition_t::name[]; -constexpr char GeckoEditable::OnImeRemoveComposition_t::signature[]; - constexpr char GeckoEditable::OnImeReplaceText_t::name[]; constexpr char GeckoEditable::OnImeReplaceText_t::signature[]; -constexpr char GeckoEditable::OnImeSetSelection_t::name[]; -constexpr char GeckoEditable::OnImeSetSelection_t::signature[]; - constexpr char GeckoEditable::OnImeSynchronize_t::name[]; constexpr char GeckoEditable::OnImeSynchronize_t::signature[]; diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h index 78784bac617..27a82af2b7a 100644 --- a/widget/android/GeneratedJNIWrappers.h +++ b/widget/android/GeneratedJNIWrappers.h @@ -1866,21 +1866,6 @@ public: mozilla::jni::ExceptionMode::ABORT; }; -public: - struct OnImeRemoveComposition_t { - typedef GeckoEditable Owner; - typedef void ReturnType; - typedef void SetterType; - typedef mozilla::jni::Args<> Args; - static constexpr char name[] = "onImeRemoveComposition"; - static constexpr char signature[] = - "()V"; - static const bool isStatic = false; - static const bool isMultithreaded = false; - static const mozilla::jni::ExceptionMode exceptionMode = - mozilla::jni::ExceptionMode::ABORT; - }; - public: struct OnImeReplaceText_t { typedef GeckoEditable Owner; @@ -1889,28 +1874,10 @@ public: typedef mozilla::jni::Args< int32_t, int32_t, - mozilla::jni::String::Param, - bool> Args; + mozilla::jni::String::Param> Args; static constexpr char name[] = "onImeReplaceText"; static constexpr char signature[] = - "(IILjava/lang/String;Z)V"; - static const bool isStatic = false; - static const bool isMultithreaded = false; - static const mozilla::jni::ExceptionMode exceptionMode = - mozilla::jni::ExceptionMode::ABORT; - }; - -public: - struct OnImeSetSelection_t { - typedef GeckoEditable Owner; - typedef void ReturnType; - typedef void SetterType; - typedef mozilla::jni::Args< - int32_t, - int32_t> Args; - static constexpr char name[] = "onImeSetSelection"; - static constexpr char signature[] = - "(II)V"; + "(IILjava/lang/String;)V"; static const bool isStatic = false; static const bool isMultithreaded = false; static const mozilla::jni::ExceptionMode exceptionMode = diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index a3679815482..4785d6652f3 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -206,8 +206,6 @@ class nsWindow::Natives final // Events that result in user-visible changes count as UI events. if (Base::lambda.IsTarget(&Natives::OnKeyEvent) || Base::lambda.IsTarget(&Natives::OnImeReplaceText) || - Base::lambda.IsTarget(&Natives::OnImeSetSelection) || - Base::lambda.IsTarget(&Natives::OnImeRemoveComposition) || Base::lambda.IsTarget(&Natives::OnImeUpdateComposition)) { return nsAppShell::Event::Type::kUIActivity; @@ -241,7 +239,6 @@ public: , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet , mIMEUpdatingContext(false) , mIMESelectionChanged(false) - , mIMEMaskSelectionUpdate(false) {} ~Natives(); @@ -279,8 +276,8 @@ private: * Gecko controls the text content, and Java shadows the Gecko text through text updates - * Java controls the selection, and Gecko shadows the Java selection - through set selection events + * Gecko and Java maintain separate selections, and synchronize when + needed through selection updates and set-selection events * Java controls the composition, and Gecko shadows the Java composition through update composition events */ @@ -317,7 +314,6 @@ private: int32_t mIMEMaskEventsCount; // Mask events when > 0 bool mIMEUpdatingContext; bool mIMESelectionChanged; - bool mIMEMaskSelectionUpdate; void SendIMEDummyKeyEvents(); void AddIMETextChange(const IMETextChange& aChange); @@ -345,13 +341,7 @@ public: // Replace a range of text with new text. void OnImeReplaceText(int32_t aStart, int32_t aEnd, - jni::String::Param aText, bool aComposing); - - // Set selection to a certain range. - void OnImeSetSelection(int32_t aStart, int32_t aEnd); - - // Remove any active composition. - void OnImeRemoveComposition(); + jni::String::Param aText); // Add styling for a range within the active composition. void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd, @@ -1914,18 +1904,6 @@ ConvertAndroidColor(uint32_t aArgb) (aArgb & 0xff000000) >> 24); } -class AutoIMEMask { -private: - bool mOldMask, *mMask; -public: - AutoIMEMask(bool &aMask) : mOldMask(aMask), mMask(&aMask) { - aMask = true; - } - ~AutoIMEMask() { - *mMask = mOldMask; - } -}; - /* * Get the current composition object, if any. */ @@ -2164,10 +2142,6 @@ nsWindow::Natives::NotifyIME(const IMENotification& aIMENotification) } case NOTIFY_IME_OF_SELECTION_CHANGE: { - if (mIMEMaskSelectionUpdate) { - return true; - } - ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE"); PostFlushIMEChanges(); @@ -2274,7 +2248,6 @@ nsWindow::Natives::OnImeSynchronize() void nsWindow::Natives::OnImeAcknowledgeFocus() { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); MOZ_ASSERT(mIMEMaskEventsCount > 0); if (--mIMEMaskEventsCount > 0) { @@ -2300,10 +2273,8 @@ nsWindow::Natives::OnImeAcknowledgeFocus() void nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, - jni::String::Param aText, bool aComposing) + jni::String::Param aText) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused; still reply to events, but don't do anything else. return OnImeSynchronize(); @@ -2311,12 +2282,8 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, /* Replace text in Gecko thread from aStart to aEnd with the string text. - - Selection updates are masked so the result of our temporary - selection event is not passed on to Java */ RefPtr kungFuDeathGrip(&window); - AutoIMEMask selMask(mIMEMaskSelectionUpdate); nsString string(aText); const auto composition(window.GetIMEComposition()); @@ -2379,25 +2346,37 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, AddIMETextChange(dummyChange); } + const bool composing = !mIMERanges->IsEmpty(); + // Previous events may have destroyed our composition; bail in that case. if (window.GetIMEComposition()) { WidgetCompositionEvent event(true, eCompositionChange, &window); window.InitEvent(event, nullptr); event.mData = string; - // Include proper text ranges to make the editor happy. - TextRange range; - range.mStartOffset = 0; - range.mEndOffset = event.mData.Length(); - range.mRangeType = NS_TEXTRANGE_RAWINPUT; - event.mRanges = new TextRangeArray(); - event.mRanges->AppendElement(range); + if (composing) { + event.mRanges = new TextRangeArray(); + mIMERanges.swap(event.mRanges); + + } else if (event.mData.Length()) { + // Include proper text ranges to make the editor happy. + TextRange range; + range.mStartOffset = 0; + range.mEndOffset = event.mData.Length(); + range.mRangeType = NS_TEXTRANGE_RAWINPUT; + event.mRanges = new TextRangeArray(); + event.mRanges->AppendElement(range); + } window.DispatchEvent(&event); + + } else if (composing) { + // Ensure IME ranges are empty. + mIMERanges->Clear(); } // Don't end composition when composing text or composition was destroyed. - if (!aComposing) { + if (!composing) { window.RemoveIMEComposition(); } @@ -2407,79 +2386,12 @@ nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd, OnImeSynchronize(); } -void -nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd) -{ - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - - if (mIMEMaskEventsCount > 0) { - // Not focused. - return; - } - - /* - Set Gecko selection to aStart to aEnd. - - Selection updates are masked to prevent Java from being - notified of the new selection - */ - RefPtr kungFuDeathGrip(&window); - AutoIMEMask selMask(mIMEMaskSelectionUpdate); - WidgetSelectionEvent selEvent(true, eSetSelection, &window); - - window.InitEvent(selEvent, nullptr); - window.RemoveIMEComposition(); - - if (aStart < 0 || aEnd < 0) { - WidgetQueryContentEvent event(true, eQuerySelectedText, &window); - window.InitEvent(event, nullptr); - window.DispatchEvent(&event); - MOZ_ASSERT(event.mSucceeded); - - if (aStart < 0) - aStart = int32_t(event.GetSelectionStart()); - if (aEnd < 0) - aEnd = int32_t(event.GetSelectionEnd()); - } - - selEvent.mOffset = std::min(aStart, aEnd); - selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset; - selEvent.mReversed = aStart > aEnd; - selEvent.mExpandToClusterBoundary = false; - - window.DispatchEvent(&selEvent); -} - -void -nsWindow::Natives::OnImeRemoveComposition() -{ - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - - if (mIMEMaskEventsCount > 0) { - // Not focused. - return; - } - - /* - * Remove any previous composition. This is only used for - * visual indication and does not affect the text content. - * - * Selection updates are masked so the result of - * temporary events are not passed on to Java - */ - AutoIMEMask selMask(mIMEMaskSelectionUpdate); - window.RemoveIMEComposition(); - mIMERanges->Clear(); -} - void nsWindow::Natives::OnImeAddCompositionRange( int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle, int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor, int32_t aRangeBackColor, int32_t aRangeLineColor) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused. return; @@ -2504,13 +2416,28 @@ nsWindow::Natives::OnImeAddCompositionRange( void nsWindow::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd) { - MOZ_ASSERT(!mIMEMaskSelectionUpdate); - if (mIMEMaskEventsCount > 0) { // Not focused. return; } + // A composition with no ranges means we want to set the selection. + if (mIMERanges->IsEmpty()) { + MOZ_ASSERT(aStart >= 0 && aEnd >= 0); + window.RemoveIMEComposition(); + + WidgetSelectionEvent selEvent(true, eSetSelection, &window); + window.InitEvent(selEvent, nullptr); + + selEvent.mOffset = std::min(aStart, aEnd); + selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset; + selEvent.mReversed = aStart > aEnd; + selEvent.mExpandToClusterBoundary = false; + + window.DispatchEvent(&selEvent); + return; + } + /* Update the composition from aStart to aEnd using information from added ranges. This is only used for @@ -2518,12 +2445,8 @@ nsWindow::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd) Only the offsets are specified and not the text content to eliminate the possibility of this event altering the text content unintentionally. - - Selection updates are masked so the result of - temporary events are not passed on to Java */ RefPtr kungFuDeathGrip(&window); - AutoIMEMask selMask(mIMEMaskSelectionUpdate); const auto composition(window.GetIMEComposition()); MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent()); diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 7c520d094a0..6769051dc2c 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1101,6 +1101,12 @@ GfxInfo::GetGfxDriverInfo() nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2869)); + /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE(DRIVER_OS_ALL, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(9,17,10,0), V(9,17,10,2849), "Intel driver > 9.17.10.2849"); + APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia8800GTS), nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,