author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Mon, 22 Jun 2015 14:03:17 +0200 | |
changeset 249958 | be81b8d6fae99c89e8b14591b11dd26eec0a416e |
parent 249846 | cddf3b36b5e2c2d9553f71c83a3ae943da73006f (current diff) |
parent 249957 | 026e77985e59fa57715aa30b3117091caeaba597 (diff) |
child 249971 | 006adfdd7855a89e122e3018eb837e24e3accf4d |
child 249993 | 53fd04b756f04ec78c059a6dae85b8559e2f3723 |
child 250010 | 4b47c3f074a36bb16295bf3127268094a64ed7db |
push id | 28940 |
push user | cbook@mozilla.com |
push date | Mon, 22 Jun 2015 12:03:34 +0000 |
treeherder | mozilla-central@be81b8d6fae9 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 41.0a1 |
first release with | nightly linux32
be81b8d6fae9
/
41.0a1
/
20150622052959
/
files
nightly linux64
be81b8d6fae9
/
41.0a1
/
20150622052959
/
files
nightly mac
be81b8d6fae9
/
41.0a1
/
20150622052959
/
files
nightly win32
be81b8d6fae9
/
41.0a1
/
20150622052959
/
files
nightly win64
be81b8d6fae9
/
41.0a1
/
20150622052959
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
41.0a1
/
20150622052959
/
pushlog to previous
nightly linux64
41.0a1
/
20150622052959
/
pushlog to previous
nightly mac
41.0a1
/
20150622052959
/
pushlog to previous
nightly win32
41.0a1
/
20150622052959
/
pushlog to previous
nightly win64
41.0a1
/
20150622052959
/
pushlog to previous
|
--- a/addon-sdk/source/lib/sdk/io/buffer.js +++ b/addon-sdk/source/lib/sdk/io/buffer.js @@ -242,21 +242,21 @@ Object.defineProperties(Buffer.prototype return buffer; } }, write: { value: function(string, offset, length, encoding = 'utf8') { // write(string, encoding); if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) { - ([offset, length, encoding]) = [0, null, offset]; + [offset, length, encoding] = [0, null, offset]; } // write(string, offset, encoding); else if (typeof(length) === 'string') - ([length, encoding]) = [null, length]; + [length, encoding] = [null, length]; if (offset < 0 || offset > this.length) throw new RangeError('offset is outside of valid range'); offset = ~~offset; // Clamp length if it would overflow buffer, or if its // undefined
--- a/addon-sdk/source/lib/sdk/panel/utils.js +++ b/addon-sdk/source/lib/sdk/panel/utils.js @@ -124,33 +124,33 @@ function display(panel, options, anchor) if (!anchor) { // The XUL Panel doesn't have an arrow, so the margin needs to be reset // in order to, be positioned properly panel.style.margin = "0"; let viewportRect = document.defaultView.gBrowser.getBoundingClientRect(); - ({x, y, width, height}) = calculateRegion(options, viewportRect); + ({x, y, width, height} = calculateRegion(options, viewportRect)); } else { // The XUL Panel has an arrow, so the margin needs to be reset // to the default value. panel.style.margin = ""; let { CustomizableUI, window } = anchor.ownerDocument.defaultView; // In Australis, widgets may be positioned in an overflow panel or the // menu panel. // In such cases clicking this widget will hide the overflow/menu panel, // and the widget's panel will show instead. // If `CustomizableUI` is not available, it means the anchor is not in a // chrome browser window, and therefore there is no need for this check. if (CustomizableUI) { let node = anchor; - ({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window); + ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window)); // if `node` is not the `anchor` itself, it means the widget is // positioned in a panel, therefore we have to hide it before show // the widget's panel in the same anchor if (node !== anchor) CustomizableUI.hidePanelForNode(anchor); }
--- a/addon-sdk/source/lib/sdk/util/sequence.js +++ b/addon-sdk/source/lib/sdk/util/sequence.js @@ -240,17 +240,17 @@ const map = (f, ...sequences) => seq(fun // Run loop yielding of applying `f` to the set of // items at each step until one of the `inputs` is // exhausted. let done = false; while (!done) { let index = 0; let value = void(0); while (index < count && !done) { - ({ done, value }) = inputs[index].next(); + ({ done, value } = inputs[index].next()); // If input is not exhausted yet store value in args. if (!done) { args[index] = value; index = index + 1; } } @@ -268,20 +268,20 @@ exports.map = map; // // Implements clojure reductions: // http://clojuredocs.org/clojure_core/clojure.core/reductions const reductions = (...params) => { const count = params.length; let hasInitial = false; let f, initial, source; if (count === 2) { - ([f, source]) = params; + [f, source] = params; } else if (count === 3) { - ([f, initial, source]) = params; + [f, initial, source] = params; hasInitial = true; } else { throw Error("Invoked with wrong number of arguments: " + count); } const sequence = seq(source);
--- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -217,16 +217,20 @@ pref("content.sink.perf_parse_time", 500 pref("dom.use_watchdog", false); // The slow script dialog can be triggered from inside the JS engine as well, // ensure that those calls don't accidentally trigger the dialog. pref("dom.max_script_run_time", 0); pref("dom.max_chrome_script_run_time", 0); pref("dom.max_child_script_run_time", 0); +// Temporarily disable support for offsetX/Y to work around Google Maps bug +// (bug 1150284) +pref("dom.mouseEvent.offsetXY.enabled", false); + // plugins pref("plugin.disable", true); pref("dom.ipc.plugins.enabled", true); // product URLs // The breakpad report server to link to in about:crashes pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/"); pref("app.releaseNotesURL", "http://www.mozilla.com/%LOCALE%/b2g/%VERSION%/releasenotes/");
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -624,17 +624,17 @@ pref("mousewheel.with_meta.action", 1); #endif pref("mousewheel.with_control.action",3); pref("mousewheel.with_win.action", 1); pref("browser.xul.error_pages.enabled", true); pref("browser.xul.error_pages.expert_bad_cert", false); // If true, network link events will change the value of navigator.onLine -pref("network.manage-offline-status", false); +pref("network.manage-offline-status", true); // We want to make sure mail URLs are handled externally... pref("network.protocol-handler.external.mailto", true); // for mail pref("network.protocol-handler.external.news", true); // for news pref("network.protocol-handler.external.snews", true); // for secure news pref("network.protocol-handler.external.nntp", true); // also news #ifdef XP_WIN pref("network.protocol-handler.external.ms-windows-store", true); @@ -1947,9 +1947,10 @@ pref("browser.pocket.oAuthConsumerKey", pref("browser.pocket.useLocaleList", true); pref("browser.pocket.enabledLocales", "en-US de es-ES ja ja-JP-mac ru"); pref("view_source.tab", true); // Enable Service Workers for desktop on non-release builds #ifndef RELEASE_BUILD pref("dom.serviceWorkers.enabled", true); +pref("dom.serviceWorkers.interception.enabled", true); #endif
--- a/browser/base/content/pageinfo/permissions.js +++ b/browser/base/content/pageinfo/permissions.js @@ -201,16 +201,20 @@ function initIndexedDBRow() } function onIndexedDBClear() { Components.classes["@mozilla.org/dom/quota/manager;1"] .getService(nsIQuotaManager) .clearStoragesForURI(gPermURI); + Components.classes["@mozilla.org/serviceworkers/manager;1"] + .getService(Components.interfaces.nsIServiceWorkerManager) + .removeAndPropagate(gPermURI.host); + SitePermissions.remove(gPermURI, "indexedDB"); initIndexedDBRow(); } function onIndexedDBUsageCallback(uri, usage, fileUsage) { if (!uri.equals(gPermURI)) { throw new Error("Callback received for bad URI: " + uri);
--- a/browser/devtools/storage/test/storage-complex-values.html +++ b/browser/devtools/storage/test/storage-complex-values.html @@ -1,9 +1,9 @@ -<!DOCTYPE HTML> +<!DOCTYPE HTML> <html> <!-- Bug 970517 - Storage inspector front end - tests --> <head> <meta charset="utf-8"> <title>Storage inspector test for correct values in the sidebar</title> </head> @@ -36,16 +36,17 @@ console.log("added cookies and stuff fro function success(event) { setupIDB.next(event); } window.idbGenerator = function*(callback) { let request = indexedDB.open("idb1", 1); request.onupgradeneeded = success; + request.onsuccess = success; request.onerror = function(e) { throw new Error("error opening db connection"); }; let event = yield undefined; let db = event.target.result; let store1 = db.createObjectStore("obj1", { keyPath: "id" }); store1.createIndex("name", "name", { unique: false }); store1.createIndex("email", "email", { unique: true }); @@ -55,28 +56,28 @@ window.idbGenerator = function*(callback yield undefined; store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}).onsuccess = success; yield undefined; store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}).onsuccess = success; yield undefined; store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"}).onsuccess = success; yield undefined; - store1.transaction.oncomplete = success; yield undefined; db.close(); request = indexedDB.open("idb2", 1); request.onupgradeneeded = success; + request.onsuccess = success; event = yield undefined; let db2 = event.target.result; let store3 = db2.createObjectStore("obj3", { keyPath: "id3" }); store3.createIndex("name2", "name2", { unique: true }); - store3.transaction.oncomplete = success; + yield undefined; db2.close(); console.log("added cookies and stuff from main page"); callback(); } function successClear(event) { clearIterator.next(event);
--- a/browser/devtools/storage/test/storage-listings.html +++ b/browser/devtools/storage/test/storage-listings.html @@ -1,9 +1,9 @@ -<!DOCTYPE HTML> +<!DOCTYPE HTML> <html> <!-- Bug 970517 - Storage inspector front end - tests --> <head> <meta charset="utf-8"> <title>Storage inspector test for listing hosts and storages</title> </head> @@ -30,16 +30,17 @@ console.log("added cookies and stuff fro function success(event) { setupIDB.next(event); } window.idbGenerator = function*(callback) { let request = indexedDB.open("idb1", 1); request.onupgradeneeded = success; + request.onsuccess = success; request.onerror = function(e) { throw new Error("error opening db connection"); }; let event = yield undefined; let db = event.target.result; let store1 = db.createObjectStore("obj1", { keyPath: "id" }); store1.createIndex("name", "name", { unique: false }); store1.createIndex("email", "email", { unique: true }); @@ -49,28 +50,28 @@ window.idbGenerator = function*(callback yield undefined; store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}).onsuccess = success; yield undefined; store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}).onsuccess = success; yield undefined; store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"}).onsuccess = success; yield undefined; - store1.transaction.oncomplete = success; yield undefined; db.close(); request = indexedDB.open("idb2", 1); request.onupgradeneeded = success; + request.onsuccess = success; event = yield undefined; let db2 = event.target.result; let store3 = db2.createObjectStore("obj3", { keyPath: "id3" }); store3.createIndex("name2", "name2", { unique: true }); - store3.transaction.oncomplete = success; + yield undefined; db2.close(); console.log("added cookies and stuff from main page"); callback(); } function successClear(event) { clearIterator.next(event);
--- a/browser/devtools/storage/test/storage-secured-iframe.html +++ b/browser/devtools/storage/test/storage-secured-iframe.html @@ -1,9 +1,9 @@ -<!DOCTYPE HTML> +<!DOCTYPE HTML> <html> <!-- Iframe for testing multiple host detetion in storage actor --> <head> <meta charset="utf-8"> </head> <body> @@ -15,41 +15,43 @@ sessionStorage.setItem("iframe-s-ss1", " function success(event) { setupIDB.next(event); } window.idbGenerator = function*(callback) { let request = indexedDB.open("idb-s1", 1); request.onupgradeneeded = success; + request.onsuccess = success; request.onerror = function(e) { throw new Error("error opening db connection"); }; let event = yield undefined; let db = event.target.result; let store1 = db.createObjectStore("obj-s1", { keyPath: "id" }); store1.add({id: 6, name: "foo", email: "foo@bar.com"}).onsuccess = success; yield undefined; store1.add({id: 7, name: "foo2", email: "foo2@bar.com"}).onsuccess = success; yield undefined; - store1.transaction.oncomplete = success; + yield undefined; db.close(); request = indexedDB.open("idb-s2", 1); request.onupgradeneeded = success; + request.onsuccess = success; event = yield undefined; let db2 = event.target.result; let store3 = db2.createObjectStore("obj-s2", { keyPath: "id3", autoIncrement: true }); store3.createIndex("name2", "name2", { unique: true }); store3.add({id3: 16, name2: "foo", email: "foo@bar.com"}).onsuccess = success; yield undefined; - store3.transaction.oncomplete = success; + yield undefined; db2.close(); console.log("added cookies and stuff from secured iframe"); callback(); } function successClear(event) { clearIterator.next(event);
--- a/build/mobile/remoteautomation.py +++ b/build/mobile/remoteautomation.py @@ -7,17 +7,17 @@ import time import re import os import tempfile import shutil import subprocess import sys from automation import Automation -from devicemanager import DMError +from devicemanager import DMError, DeviceManager from mozlog.structured import get_default_logger import mozcrash # signatures for logcat messages that we don't care about much fennecLogcatFilters = [ "The character encoding of the HTML document was not declared", "Use of Mutation Events is deprecated. Use MutationObserver instead.", "Unexpected value from nativeGetEnabledTags: 0" ] @@ -119,18 +119,20 @@ class RemoteAutomation(Automation): return status def deleteANRs(self): # empty ANR traces.txt file; usually need root permissions # we make it empty and writable so we can test the ANR reporter later traces = "/data/anr/traces.txt" try: - self._devicemanager.shellCheckOutput(['echo', '', '>', traces], root=True) - self._devicemanager.shellCheckOutput(['chmod', '666', traces], root=True) + self._devicemanager.shellCheckOutput(['echo', '', '>', traces], root=True, + timeout=DeviceManager.short_timeout) + self._devicemanager.shellCheckOutput(['chmod', '666', traces], root=True, + timeout=DeviceManager.short_timeout) except DMError: print "Error deleting %s" % traces pass def checkForANRs(self): traces = "/data/anr/traces.txt" if self._devicemanager.fileExists(traces): try: @@ -145,33 +147,36 @@ class RemoteAutomation(Automation): print "Error pulling %s" % traces else: print "%s not found" % traces def deleteTombstones(self): # delete any existing tombstone files from device remoteDir = "/data/tombstones" try: - self._devicemanager.shellCheckOutput(['rm', '-r', remoteDir], root=True) + self._devicemanager.shellCheckOutput(['rm', '-r', remoteDir], root=True, + timeout=DeviceManager.short_timeout) except DMError: # This may just indicate that the tombstone directory is missing pass def checkForTombstones(self): # pull any tombstones from device and move to MOZ_UPLOAD_DIR remoteDir = "/data/tombstones" blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None) if blobberUploadDir: if not os.path.exists(blobberUploadDir): os.mkdir(blobberUploadDir) if self._devicemanager.dirExists(remoteDir): # copy tombstone files from device to local blobber upload directory try: - self._devicemanager.shellCheckOutput(['chmod', '777', remoteDir], root=True) - self._devicemanager.shellCheckOutput(['chmod', '666', os.path.join(remoteDir, '*')], root=True) + self._devicemanager.shellCheckOutput(['chmod', '777', remoteDir], root=True, + timeout=DeviceManager.short_timeout) + self._devicemanager.shellCheckOutput(['chmod', '666', os.path.join(remoteDir, '*')], root=True, + timeout=DeviceManager.short_timeout) self._devicemanager.getDirectory(remoteDir, blobberUploadDir, False) except DMError: # This may just indicate that no tombstone files are present pass self.deleteTombstones() # add a .txt file extension to each tombstone file name, so # that blobber will upload it for f in glob.glob(os.path.join(blobberUploadDir, "tombstone_??")): @@ -187,22 +192,17 @@ class RemoteAutomation(Automation): print "%s does not exist; tombstone check skipped" % remoteDir else: print "MOZ_UPLOAD_DIR not defined; tombstone check skipped" def checkForCrashes(self, directory, symbolsPath): self.checkForANRs() self.checkForTombstones() - try: - logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters) - except DMError: - print "getLogcat threw DMError; re-trying just once..." - time.sleep(1) - logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters) + logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters) javaException = mozcrash.check_for_java_exception(logcat) if javaException: return True # If crash reporting is disabled (MOZ_CRASHREPORTER!=1), we can't say # anything. if not self.CRASHREPORTER:
--- a/caps/moz.build +++ b/caps/moz.build @@ -1,16 +1,16 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -TEST_DIRS += ['tests/mochitest'] - +MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini'] +MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini'] XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini'] XPIDL_SOURCES += [ 'nsIDomainPolicy.idl', 'nsIPrincipal.idl', 'nsIScriptSecurityManager.idl', ]
deleted file mode 100644 --- a/caps/tests/mochitest/moz.build +++ /dev/null @@ -1,9 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -MOCHITEST_MANIFESTS += ['mochitest.ini'] -MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] -
--- a/db/sqlite3/src/sqlite.def +++ b/db/sqlite3/src/sqlite.def @@ -102,16 +102,17 @@ EXPORTS sqlite3_realloc sqlite3_release_memory sqlite3_reset sqlite3_reset_auto_extension sqlite3_result_blob sqlite3_result_double sqlite3_result_error sqlite3_result_error16 + sqlite3_result_error_code sqlite3_result_error_nomem sqlite3_result_int sqlite3_result_int64 sqlite3_result_null sqlite3_result_text sqlite3_result_text16 sqlite3_result_text16be sqlite3_result_text16le
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -219,20 +219,22 @@ static NS_DEFINE_CID(kAppShellCID, NS_AP #else #include <unistd.h> // for getpid() #endif using namespace mozilla; using namespace mozilla::dom; using mozilla::dom::workers::ServiceWorkerManager; -// True means sUseErrorPages has been added to preferences var cache. +// True means sUseErrorPages and sInterceptionEnabled has been added to +// preferences var cache. static bool gAddedPreferencesVarCache = false; bool nsDocShell::sUseErrorPages = false; +bool nsDocShell::sInterceptionEnabled = false; // Number of documents currently loading static int32_t gNumberOfDocumentsLoading = 0; // Global count of existing docshells. static int32_t gDocShellCount = 0; // Global count of docshells with the private attribute set @@ -5736,16 +5738,19 @@ nsDocShell::Create() // Should we use XUL error pages instead of alerts if possible? mUseErrorPages = Preferences::GetBool("browser.xul.error_pages.enabled", mUseErrorPages); if (!gAddedPreferencesVarCache) { Preferences::AddBoolVarCache(&sUseErrorPages, "browser.xul.error_pages.enabled", mUseErrorPages); + Preferences::AddBoolVarCache(&sInterceptionEnabled, + "dom.serviceWorkers.interception.enabled", + false); gAddedPreferencesVarCache = true; } mDeviceSizeIsPageSize = Preferences::GetBool("docshell.device_size_is_page_size", mDeviceSizeIsPageSize); nsCOMPtr<nsIObserverService> serv = services::GetObserverService(); @@ -14042,16 +14047,21 @@ nsDocShell::MaybeNotifyKeywordSearchLoad #endif } NS_IMETHODIMP nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNavigate, bool* aShouldIntercept) { *aShouldIntercept = false; + // Preffed off. + if (!sInterceptionEnabled) { + return NS_OK; + } + if (mSandboxFlags) { // If we're sandboxed, don't intercept. return NS_OK; } nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); if (!swm) { return NS_OK;
--- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -898,16 +898,19 @@ protected: PARENT_ALLOWS, PARENT_PROHIBITS }; FullscreenAllowedState mFullscreenAllowed; // Cached value of the "browser.xul.error_pages.enabled" preference. static bool sUseErrorPages; + // Cached value of the "dom.serviceWorkers.interception.enabled" preference. + static bool sInterceptionEnabled; + bool mCreated; bool mAllowSubframes; bool mAllowPlugins; bool mAllowJavascript; bool mAllowMetaRedirects; bool mAllowImages; bool mAllowMedia; bool mAllowDNSPrefetch;
--- a/dom/base/Link.cpp +++ b/dom/base/Link.cpp @@ -439,17 +439,17 @@ Link::GetHash(nsAString &_hash, ErrorRes // string. return; } nsAutoCString ref; nsresult rv = uri->GetRef(ref); if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) { _hash.Assign(char16_t('#')); - if (nsContentUtils::EncodeDecodeURLHash()) { + if (nsContentUtils::GettersDecodeURLHash()) { NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes! } AppendUTF8toUTF16(ref, _hash); } } void Link::ResetLinkState(bool aNotify, bool aHasHref)
--- a/dom/base/URL.cpp +++ b/dom/base/URL.cpp @@ -520,17 +520,17 @@ void URL::GetHash(nsAString& aHash, ErrorResult& aRv) const { aHash.Truncate(); nsAutoCString ref; nsresult rv = mURI->GetRef(ref); if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) { aHash.Assign(char16_t('#')); - if (nsContentUtils::EncodeDecodeURLHash()) { + if (nsContentUtils::GettersDecodeURLHash()) { NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes! } AppendUTF8toUTF16(ref, aHash); } } void URL::SetHash(const nsAString& aHash, ErrorResult& aRv)
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -251,16 +251,17 @@ bool nsContentUtils::sInitialized = fals bool nsContentUtils::sIsFullScreenApiEnabled = false; bool nsContentUtils::sTrustedFullScreenOnly = true; bool nsContentUtils::sIsCutCopyAllowed = true; bool nsContentUtils::sIsPerformanceTimingEnabled = false; bool nsContentUtils::sIsResourceTimingEnabled = false; bool nsContentUtils::sIsUserTimingLoggingEnabled = false; bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; bool nsContentUtils::sEncodeDecodeURLHash = false; +bool nsContentUtils::sGettersDecodeURLHash = false; bool nsContentUtils::sPrivacyResistFingerprinting = false; uint32_t nsContentUtils::sHandlingInputTimeout = 1000; nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr; nsIParser* nsContentUtils::sXMLFragmentParser = nullptr; nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr; bool nsContentUtils::sFragmentParsingActive = false; @@ -533,16 +534,19 @@ nsContentUtils::Init() "dom.performance.enable_user_timing_logging", false); Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled, "dom.forms.autocomplete.experimental", false); Preferences::AddBoolVarCache(&sEncodeDecodeURLHash, "dom.url.encode_decode_hash", false); + Preferences::AddBoolVarCache(&sGettersDecodeURLHash, + "dom.url.getters_decode_hash", false); + Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting, "privacy.resistFingerprinting", false); Preferences::AddUintVarCache(&sHandlingInputTimeout, "dom.event.handling-user-input-time-limit", 1000); #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) @@ -6308,20 +6312,17 @@ nsContentUtils::FlushLayoutForTree(nsIDO nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow); if (!piWin) return; // Note that because FlushPendingNotifications flushes parents, this // is O(N^2) in docshell tree depth. However, the docshell tree is // usually pretty shallow. - nsCOMPtr<nsIDOMDocument> domDoc; - aWindow->GetDocument(getter_AddRefs(domDoc)); - nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); - if (doc) { + if (nsCOMPtr<nsIDocument> doc = piWin->GetDoc()) { doc->FlushPendingNotifications(Flush_Layout); } nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell(); if (docShell) { int32_t i = 0, i_end; docShell->GetChildCount(&i_end); for (; i < i_end; ++i) {
--- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1944,16 +1944,24 @@ public: * and getters should return the percent decoded value of the segment */ static bool EncodeDecodeURLHash() { return sEncodeDecodeURLHash; } /* + * Returns true if URL getters should percent decode the value of the segment + */ + static bool GettersDecodeURLHash() + { + return sGettersDecodeURLHash && sEncodeDecodeURLHash; + } + + /* * Returns true if the browser should attempt to prevent content scripts * from collecting distinctive information about the browser that could * be used to "fingerprint" and track the user across websites. */ static bool ResistFingerprinting() { return sPrivacyResistFingerprinting; } @@ -2494,16 +2502,17 @@ private: static bool sTrustedFullScreenOnly; static bool sIsCutCopyAllowed; static uint32_t sHandlingInputTimeout; static bool sIsPerformanceTimingEnabled; static bool sIsResourceTimingEnabled; static bool sIsUserTimingLoggingEnabled; static bool sIsExperimentalAutocompleteEnabled; static bool sEncodeDecodeURLHash; + static bool sGettersDecodeURLHash; static bool sPrivacyResistFingerprinting; static nsHtml5StringParser* sHTMLFragmentParser; static nsIParser* sXMLFragmentParser; static nsIFragmentContentSink* sXMLFragmentSink; /** * True if there's a fragment parser activation on the stack.
--- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -1780,18 +1780,17 @@ nsMessageManagerScriptExecutor::TryCache JS::Rooted<JSScript*> script(cx); if (aRunInGlobalScope) { if (!JS::Compile(cx, options, srcBuf, &script)) { return; } } else { // We're going to run these against some non-global scope. - options.setHasPollutedScope(true); - if (!JS::Compile(cx, options, srcBuf, &script)) { + if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) { return; } } aScriptp.set(script); nsAutoCString scheme; uri->GetScheme(scheme);
--- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -82,17 +82,16 @@ nsImageLoadingContent::nsImageLoadingCon mImageBlockingStatus(nsIContentPolicy::ACCEPT), mLoadingEnabled(true), mIsImageStateForced(false), mLoading(false), // mBroken starts out true, since an image without a URI is broken.... mBroken(true), mUserDisabled(false), mSuppressed(false), - mFireEventsOnDecode(false), mNewRequestsWillNeedAnimationReset(false), mStateChangerDepth(0), mCurrentRequestRegistered(false), mPendingRequestRegistered(false), mFrameCreateCalled(false), mVisibleCount(0) { if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) { @@ -181,28 +180,16 @@ nsImageLoadingContent::Notify(imgIReques } } nsresult status = reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; return OnLoadComplete(aRequest, status); } if (aType == imgINotificationObserver::DECODE_COMPLETE) { - if (mFireEventsOnDecode) { - mFireEventsOnDecode = false; - - uint32_t reqStatus; - aRequest->GetImageStatus(&reqStatus); - if (reqStatus & imgIRequest::STATUS_ERROR) { - FireEvent(NS_LITERAL_STRING("error")); - } else { - FireEvent(NS_LITERAL_STRING("load")); - } - } - UpdateImageState(true); } return NS_OK; } nsresult nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) @@ -272,33 +259,21 @@ nsImageLoadingContent::OnLoadComplete(im // visible. if (!mFrameCreateCalled || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) || mVisibleCount > 0 || shell->AssumeAllImagesVisible()) { mCurrentRequest->StartDecoding(); } } } - // We want to give the decoder a chance to find errors. If we haven't found - // an error yet and we've started decoding, either from the above - // StartDecoding or from some other place, we must only fire these events - // after we finish decoding. - uint32_t reqStatus; - aRequest->GetImageStatus(&reqStatus); - if (NS_SUCCEEDED(aStatus) && !(reqStatus & imgIRequest::STATUS_ERROR) && - (reqStatus & imgIRequest::STATUS_DECODE_STARTED) && - !(reqStatus & imgIRequest::STATUS_DECODE_COMPLETE)) { - mFireEventsOnDecode = true; + // Fire the appropriate DOM event. + if (NS_SUCCEEDED(aStatus)) { + FireEvent(NS_LITERAL_STRING("load")); } else { - // Fire the appropriate DOM event. - if (NS_SUCCEEDED(aStatus)) { - FireEvent(NS_LITERAL_STRING("load")); - } else { - FireEvent(NS_LITERAL_STRING("error")); - } + FireEvent(NS_LITERAL_STRING("error")); } nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement()); return NS_OK; }
--- a/dom/base/nsImageLoadingContent.h +++ b/dom/base/nsImageLoadingContent.h @@ -405,17 +405,16 @@ private: /** * The state we had the last time we checked whether we needed to notify the * document of a state change. These are maintained by UpdateImageState. */ bool mLoading : 1; bool mBroken : 1; bool mUserDisabled : 1; bool mSuppressed : 1; - bool mFireEventsOnDecode : 1; protected: /** * A hack to get animations to reset, see bug 594771. On requests * that originate from setting .src, we mark them for needing their animation * reset when they are ready. mNewRequestsWillNeedAnimationReset is set to * true while preparing such requests (as a hack around needing to change an * interface), and the other two booleans store which of the current
--- a/dom/base/nsLocation.cpp +++ b/dom/base/nsLocation.cpp @@ -294,17 +294,17 @@ nsLocation::GetHash(nsAString& aHash) return rv; } nsAutoCString ref; nsAutoString unicodeRef; rv = uri->GetRef(ref); - if (nsContentUtils::EncodeDecodeURLHash()) { + if (nsContentUtils::GettersDecodeURLHash()) { if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsITextToSubURI> textToSubURI( do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { nsAutoCString charset; uri->GetOriginCharset(charset);
--- a/dom/base/test/test_url.html +++ b/dom/base/test/test_url.html @@ -111,17 +111,17 @@ port: '', pathname: '/', search: '?test', hash: '' }, { url: 'http://example.com/carrot#question%3f', base: undefined, error: false, - hash: '#question?' + hash: '#question%3f' }, { url: 'https://example.com:4443?', base: undefined, error: false, protocol: 'https:', port: '4443', pathname: '/', hash: '',
--- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -184,11 +184,12 @@ support-files = support-files = bug989198_embedded.html bug989198_helper.js skip-if = buildapp == 'b2g' || e10s [test_bug1096146.html] support-files = bug1096146_embedded.html [test_offsetxy.html] +skip-if = toolkit == 'android' || buildapp == 'b2g' || buildapp == 'mulet' [test_eventhandler_scoping.html] [test_bug1013412.html] skip-if = buildapp == 'b2g' # no wheel events on b2g
--- a/dom/imptests/WebIDLParser.js +++ b/dom/imptests/WebIDLParser.js @@ -915,10 +915,10 @@ parse: function (str, opt) { if (!opt) opt = {}; var tokens = tokenise(str); return parse(tokens, opt); } }; if (inNode) module.exports = obj; - else window.WebIDL2 = obj; + else self.WebIDL2 = obj; }());
--- a/dom/imptests/failures/html/dom/test_interfaces.html.json +++ b/dom/imptests/failures/html/dom/test_interfaces.html.json @@ -1,42 +1,35 @@ { "DOMException exception: existence and properties of exception interface prototype object's \"name\" property": true, - "CustomEvent interface: existence and properties of interface object": true, "EventListener interface: existence and properties of interface prototype object": true, "EventListener interface: existence and properties of interface prototype object's \"constructor\" property": true, "EventListener interface: operation handleEvent(Event)": true, "MutationObserver interface: operation observe(Node,MutationObserverInit)": true, - "Node interface: existence and properties of interface object": true, - "Document interface: existence and properties of interface object": true, "Document interface: operation prepend([object Object],[object Object])": true, "Document interface: operation append([object Object],[object Object])": true, - "XMLDocument interface: existence and properties of interface object": true, "Document interface: xmlDoc must inherit property \"prepend\" with the proper type (28)": true, "Document interface: calling prepend([object Object],[object Object]) on xmlDoc with too few arguments must throw TypeError": true, "Document interface: xmlDoc must inherit property \"append\" with the proper type (29)": true, "Document interface: calling append([object Object],[object Object]) on xmlDoc with too few arguments must throw TypeError": true, - "DocumentFragment interface: existence and properties of interface object": true, "DocumentFragment interface: operation prepend([object Object],[object Object])": true, "DocumentFragment interface: operation append([object Object],[object Object])": true, "DocumentFragment interface: document.createDocumentFragment() must inherit property \"prepend\" with the proper type (4)": true, "DocumentFragment interface: calling prepend([object Object],[object Object]) on document.createDocumentFragment() with too few arguments must throw TypeError": true, "DocumentFragment interface: document.createDocumentFragment() must inherit property \"append\" with the proper type (5)": true, "DocumentFragment interface: calling append([object Object],[object Object]) on document.createDocumentFragment() with too few arguments must throw TypeError": true, - "DocumentType interface: existence and properties of interface object": true, "DocumentType interface: operation before([object Object],[object Object])": true, "DocumentType interface: operation after([object Object],[object Object])": true, "DocumentType interface: operation replace([object Object],[object Object])": true, "DocumentType interface: document.doctype must inherit property \"before\" with the proper type (3)": true, "DocumentType interface: calling before([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true, "DocumentType interface: document.doctype must inherit property \"after\" with the proper type (4)": true, "DocumentType interface: calling after([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true, "DocumentType interface: document.doctype must inherit property \"replace\" with the proper type (5)": true, "DocumentType interface: calling replace([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true, - "Element interface: existence and properties of interface object": true, "Element interface: attribute namespaceURI": true, "Element interface: attribute prefix": true, "Element interface: attribute localName": true, "Element interface: operation prepend([object Object],[object Object])": true, "Element interface: operation append([object Object],[object Object])": true, "Element interface: operation before([object Object],[object Object])": true, "Element interface: operation after([object Object],[object Object])": true, "Element interface: operation replace([object Object],[object Object])": true, @@ -47,41 +40,53 @@ "Element interface: element must inherit property \"before\" with the proper type (25)": true, "Element interface: calling before([object Object],[object Object]) on element with too few arguments must throw TypeError": true, "Element interface: element must inherit property \"after\" with the proper type (26)": true, "Element interface: calling after([object Object],[object Object]) on element with too few arguments must throw TypeError": true, "Element interface: element must inherit property \"replace\" with the proper type (27)": true, "Element interface: calling replace([object Object],[object Object]) on element with too few arguments must throw TypeError": true, "Attr interface: existence and properties of interface object": true, "Attr interface: existence and properties of interface prototype object": true, - "CharacterData interface: existence and properties of interface object": true, "CharacterData interface: operation before([object Object],[object Object])": true, "CharacterData interface: operation after([object Object],[object Object])": true, "CharacterData interface: operation replace([object Object],[object Object])": true, - "Text interface: existence and properties of interface object": true, "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"before\" with the proper type (7)": true, "CharacterData interface: calling before([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true, "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"after\" with the proper type (8)": true, "CharacterData interface: calling after([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true, "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"replace\" with the proper type (9)": true, "CharacterData interface: calling replace([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true, - "ProcessingInstruction interface: existence and properties of interface object": true, "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"before\" with the proper type (7)": true, "CharacterData interface: calling before([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true, "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"after\" with the proper type (8)": true, "CharacterData interface: calling after([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true, "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"replace\" with the proper type (9)": true, "CharacterData interface: calling replace([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true, - "Comment interface: existence and properties of interface object": true, "CharacterData interface: document.createComment(\"abc\") must inherit property \"before\" with the proper type (7)": true, "CharacterData interface: calling before([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true, "CharacterData interface: document.createComment(\"abc\") must inherit property \"after\" with the proper type (8)": true, "CharacterData interface: calling after([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true, "CharacterData interface: document.createComment(\"abc\") must inherit property \"replace\" with the proper type (9)": true, "CharacterData interface: calling replace([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true, - "NodeFilter interface: existence and properties of interface object": true, - "NodeList interface: existence and properties of interface prototype object": true, - "DOMTokenList interface: operation add(DOMString)": true, - "DOMTokenList interface: operation remove(DOMString)": true, - "DOMTokenList interface: calling add(DOMString) on document.body.classList with too few arguments must throw TypeError": true, - "DOMTokenList interface: calling remove(DOMString) on document.body.classList with too few arguments must throw TypeError": true, - "DOMSettableTokenList interface: existence and properties of interface object": true + "NodeFilter interface: existence and properties of interface prototype object": true, + "NodeFilter interface: existence and properties of interface prototype object": true, + "NodeFilter interface: existence and properties of interface prototype object's \"constructor\" property": true, + "EventListener interface: existence and properties of interface object": true, + "EventListener interface object length": true, + "NodeFilter interface: constant FILTER_ACCEPT on interface prototype object": true, + "NodeFilter interface: constant FILTER_REJECT on interface prototype object": true, + "NodeFilter interface: constant FILTER_SKIP on interface prototype object": true, + "NodeFilter interface: constant SHOW_ALL on interface prototype object": true, + "NodeFilter interface: constant SHOW_ELEMENT on interface prototype object": true, + "NodeFilter interface: constant SHOW_ATTRIBUTE on interface prototype object": true, + "NodeFilter interface: constant SHOW_TEXT on interface prototype object": true, + "NodeFilter interface: constant SHOW_CDATA_SECTION on interface prototype object": true, + "NodeFilter interface: constant SHOW_ENTITY_REFERENCE on interface prototype object": true, + "NodeFilter interface: constant SHOW_ENTITY on interface prototype object": true, + "NodeFilter interface: constant SHOW_PROCESSING_INSTRUCTION on interface prototype object": true, + "NodeFilter interface: constant SHOW_COMMENT on interface prototype object": true, + "NodeFilter interface: constant SHOW_DOCUMENT on interface prototype object": true, + "NodeFilter interface: constant SHOW_DOCUMENT_TYPE on interface prototype object": true, + "NodeFilter interface: constant SHOW_DOCUMENT_FRAGMENT on interface prototype object": true, + "NodeFilter interface: constant SHOW_NOTATION on interface prototype object": true, + "NodeFilter interface: operation acceptNode(Node)": true, + "NodeList interface: existence and properties of interface prototype object": true }
deleted file mode 100644 --- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini +++ /dev/null @@ -1,6 +0,0 @@ -# THIS FILE IS AUTOGENERATED BY parseFailures.py - DO NOT EDIT -[DEFAULT] -support-files = - - -[test_interfaces.html.json]
deleted file mode 100644 --- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "XMLHttpRequestUpload interface: existence and properties of interface object": true, - "XMLHttpRequest interface: existence and properties of interface object": true -}
--- a/dom/imptests/html/dom/test_interfaces.html +++ b/dom/imptests/html/dom/test_interfaces.html @@ -5,45 +5,16 @@ <script src=/resources/testharnessreport.js></script> <script src=/resources/WebIDLParser.js></script> <script src=/resources/idlharness.js></script> <h1>DOM IDL tests</h1> <div id=log></div> <script type=text/plain> -exception DOMException { - const unsigned short INDEX_SIZE_ERR = 1; - const unsigned short DOMSTRING_SIZE_ERR = 2; // historical - const unsigned short HIERARCHY_REQUEST_ERR = 3; - const unsigned short WRONG_DOCUMENT_ERR = 4; - const unsigned short INVALID_CHARACTER_ERR = 5; - const unsigned short NO_DATA_ALLOWED_ERR = 6; // historical - const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7; - const unsigned short NOT_FOUND_ERR = 8; - const unsigned short NOT_SUPPORTED_ERR = 9; - const unsigned short INUSE_ATTRIBUTE_ERR = 10; // historical - const unsigned short INVALID_STATE_ERR = 11; - const unsigned short SYNTAX_ERR = 12; - const unsigned short INVALID_MODIFICATION_ERR = 13; - const unsigned short NAMESPACE_ERR = 14; - const unsigned short INVALID_ACCESS_ERR = 15; - const unsigned short VALIDATION_ERR = 16; // historical - const unsigned short TYPE_MISMATCH_ERR = 17; // historical; use TypeError instead - const unsigned short SECURITY_ERR = 18; - const unsigned short NETWORK_ERR = 19; - const unsigned short ABORT_ERR = 20; - const unsigned short URL_MISMATCH_ERR = 21; - const unsigned short QUOTA_EXCEEDED_ERR = 22; - const unsigned short TIMEOUT_ERR = 23; - const unsigned short INVALID_NODE_TYPE_ERR = 24; - const unsigned short DATA_CLONE_ERR = 25; - unsigned short code; -}; - [Constructor(DOMString name)] interface DOMError { readonly attribute DOMString name; }; [Constructor(DOMString type, optional EventInit eventInitDict)] interface Event { readonly attribute DOMString type; @@ -446,30 +417,28 @@ interface DOMTokenList { }; interface DOMSettableTokenList : DOMTokenList { attribute DOMString value; }; </script> <script> "use strict"; -var xmlDoc, domException, detachedRange, element; +var xmlDoc, detachedRange, element; var idlArray; setup(function() { xmlDoc = document.implementation.createDocument(null, "", null); - try { document.appendChild(document); } catch(e) { domException = e; } detachedRange = document.createRange(); detachedRange.detach(); element = xmlDoc.createElementNS(null, "test"); element.setAttribute("bar", "baz"); idlArray = new IdlArray(); idlArray.add_idls(document.querySelector("script[type=text\\/plain]").textContent); idlArray.add_objects({ - DOMException: ['domException'], Event: ['document.createEvent("Event")', 'new Event("foo")'], CustomEvent: ['new CustomEvent("foo")'], XMLDocument: ['xmlDoc'], DOMImplementation: ['document.implementation'], DocumentFragment: ['document.createDocumentFragment()'], DocumentType: ['document.doctype'], Element: ['element'], Attr: ['document.querySelector("[id]").attributes[0]'],
--- a/dom/imptests/idlharness.js +++ b/dom/imptests/idlharness.js @@ -51,19 +51,32 @@ policies and contribution forms [3]. /// Helpers /// function constValue (cnt) { if (cnt.type === "null") return null; if (cnt.type === "NaN") return NaN; if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity; return cnt.value; } +function minOverloadLength(overloads) { + if (!overloads.length) { + return 0; + } + + return overloads.map(function(attr) { + return attr.arguments ? attr.arguments.filter(function(arg) { + return !arg.optional && !arg.variadic; + }).length : 0; + }) + .reduce(function(m, n) { return Math.min(m, n); }); +} + /// IdlArray /// // Entry point -window.IdlArray = function() +self.IdlArray = function() //@{ { /** * A map from strings to the corresponding named IdlObject, such as * IdlInterface or IdlException. These are the things that test() will run * tests on. */ this.members = {}; @@ -161,21 +174,18 @@ IdlArray.prototype.internal_add_idls = f parsed_idl.array = this; if (parsed_idl.name in this.members) { throw "Duplicate identifier " + parsed_idl.name; } switch(parsed_idl.type) { case "interface": - this.members[parsed_idl.name] = new IdlInterface(parsed_idl); - break; - - case "exception": - this.members[parsed_idl.name] = new IdlException(parsed_idl); + this.members[parsed_idl.name] = + new IdlInterface(parsed_idl, /* is_callback = */ false); break; case "dictionary": // Nothing to test, but we need the dictionary info around for type // checks this.members[parsed_idl.name] = new IdlDictionary(parsed_idl); break; @@ -188,18 +198,18 @@ IdlArray.prototype.internal_add_idls = f console.log("callback not yet supported"); break; case "enum": this.members[parsed_idl.name] = new IdlEnum(parsed_idl); break; case "callback interface": - // TODO - console.log("callback interface not yet supported"); + this.members[parsed_idl.name] = + new IdlInterface(parsed_idl, /* is_callback = */ true); break; default: throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported"; } }.bind(this)); }; @@ -408,22 +418,24 @@ IdlArray.prototype.assert_type_is = func assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " not in range [0, 4294967295]"); return; case "long long": assert_equals(typeof value, "number"); return; case "unsigned long long": + case "DOMTimeStamp": assert_equals(typeof value, "number"); assert_true(0 <= value, "unsigned long long is negative"); return; case "float": case "double": + case "DOMHighResTimeStamp": case "unrestricted float": case "unrestricted double": // TODO: distinguish these cases assert_equals(typeof value, "number"); return; case "DOMString": case "ByteString": @@ -443,23 +455,23 @@ IdlArray.prototype.assert_type_is = func } if (this.members[type] instanceof IdlInterface) { // We don't want to run the full // IdlInterface.prototype.test_instance_of, because that could result // in an infinite loop. TODO: This means we don't have tests for // NoInterfaceObject interfaces, and we also can't test objects that - // come from another window. + // come from another self. assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function"); if (value instanceof Object && !this.members[type].has_extended_attribute("NoInterfaceObject") - && type in window) + && type in self) { - assert_true(value instanceof window[type], "not instanceof " + type); + assert_true(value instanceof self[type], "not instanceof " + type); } } else if (this.members[type] instanceof IdlEnum) { assert_equals(typeof value, "string"); } else if (this.members[type] instanceof IdlDictionary) { @@ -524,21 +536,18 @@ function IdlDictionary(obj) * if there is none. */ this.base = obj.inheritance; } //@} IdlDictionary.prototype = Object.create(IdlObject.prototype); -/// IdlExceptionOrInterface /// -// Code sharing! -function IdlExceptionOrInterface(obj) -//@{ -{ +/// IdlInterface /// +function IdlInterface(obj, is_callback) { /** * obj is an object produced by the WebIDLParser.js "exception" or * "interface" production, as appropriate. */ /** Self-explanatory. */ this.name = obj.name; @@ -552,27 +561,58 @@ function IdlExceptionOrInterface(obj) */ this.untested = obj.untested; /** An array of objects produced by the "ExtAttr" production. */ this.extAttrs = obj.extAttrs; /** An array of IdlInterfaceMembers. */ this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); }); + if (this.has_extended_attribute("Unforgeable")) { + this.members + .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); }) + .forEach(function(m) { return m.isUnforgeable = true; }); + } /** * The name (as a string) of the type we inherit from, or null if there is * none. */ this.base = obj.inheritance; + + this._is_callback = is_callback; } - +IdlInterface.prototype = Object.create(IdlObject.prototype); +IdlInterface.prototype.is_callback = function() +//@{ +{ + return this._is_callback; +}; //@} -IdlExceptionOrInterface.prototype = Object.create(IdlObject.prototype); -IdlExceptionOrInterface.prototype.test = function() + +IdlInterface.prototype.has_constants = function() +//@{ +{ + return this.members.some(function(member) { + return member.type === "const"; + }); +}; +//@} + +IdlInterface.prototype.is_global = function() +//@{ +{ + return this.extAttrs.some(function(attribute) { + return attribute.name === "Global" || + attribute.name === "PrimaryGlobal"; + }); +}; +//@} + +IdlInterface.prototype.test = function() //@{ { if (this.has_extended_attribute("NoInterfaceObject")) { // No tests to do without an instance. TODO: We should still be able // to run tests on the prototype object, if we obtain one through some // other means. return; @@ -588,853 +628,604 @@ IdlExceptionOrInterface.prototype.test = // operations, . . .). These are run even if .untested is true, because // members might themselves be marked as .untested. This might happen to // interfaces if the interface itself is untested but a partial interface // that extends it is tested -- then the interface itself and its initial // members will be marked as untested, but the members added by the partial // interface are still tested. this.test_members(); }; - -//@} - -/// IdlException /// -function IdlException(obj) { IdlExceptionOrInterface.call(this, obj); } -IdlException.prototype = Object.create(IdlExceptionOrInterface.prototype); -IdlException.prototype.test_self = function() -//@{ -{ - test(function() - { - // "For every exception that is not declared with the - // [NoInterfaceObject] extended attribute, a corresponding property - // must exist on the exception’s relevant namespace object. The name of - // the property is the identifier of the exception, and its value is an - // object called the exception interface object, which provides access - // to any constants that have been associated with the exception. The - // property has the attributes { [[Writable]]: true, [[Enumerable]]: - // false, [[Configurable]]: true }." - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - var desc = Object.getOwnPropertyDescriptor(window, this.name); - assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter"); - assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter"); - assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable"); - assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable"); - assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable"); - - // "The exception interface object for a given exception must be a - // function object." - // "If an object is defined to be a function object, then it has - // characteristics as follows:" - // "Its [[Prototype]] internal property is the Function prototype - // object." - // Note: This doesn't match browsers as of December 2011, see - // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14813 - assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype, - "prototype of window's property " + format_value(this.name) + " is not Function.prototype"); - // "Its [[Get]] internal property is set as described in ECMA-262 - // section 15.3.5.4." - // Not much to test for this. - // "Its [[Construct]] internal property is set as described in ECMA-262 - // section 13.2.2." - // Tested below. - // "Its [[HasInstance]] internal property is set as described in - // ECMA-262 section 15.3.5.3, unless otherwise specified." - // TODO - // "Its [[Class]] internal property is “Function”." - // String() returns something implementation-dependent, because it - // calls Function#toString. - assert_class_string(window[this.name], "Function", - "class string of " + this.name); - - // TODO: Test 4.9.1.1. Exception interface object [[Call]] method - // (which does not match browsers: - // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14885) - }.bind(this), this.name + " exception: existence and properties of exception interface object"); - - test(function() - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - - // "The exception interface object must also have a property named - // “prototype” with attributes { [[Writable]]: false, [[Enumerable]]: - // false, [[Configurable]]: false } whose value is an object called the - // exception interface prototype object. This object also provides - // access to the constants that are declared on the exception." - assert_own_property(window[this.name], "prototype", - 'exception "' + this.name + '" does not have own property "prototype"'); - var desc = Object.getOwnPropertyDescriptor(window[this.name], "prototype"); - assert_false("get" in desc, this.name + ".prototype has getter"); - assert_false("set" in desc, this.name + ".prototype has setter"); - assert_false(desc.writable, this.name + ".prototype is writable"); - assert_false(desc.enumerable, this.name + ".prototype is enumerable"); - assert_false(desc.configurable, this.name + ".prototype is configurable"); - - // "The exception interface prototype object for a given exception must - // have an internal [[Prototype]] property whose value is as follows: - // - // "If the exception is declared to inherit from another exception, - // then the value of the internal [[Prototype]] property is the - // exception interface prototype object for the inherited exception. - // "Otherwise, the exception is not declared to inherit from another - // exception. The value of the internal [[Prototype]] property is the - // Error prototype object ([ECMA-262], section 15.11.3.1)." - // - // Note: This doesn't match browsers as of December 2011, see - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14887. - var inherit_exception = this.base ? this.base : "Error"; - assert_own_property(window, inherit_exception, - 'should inherit from ' + inherit_exception + ', but window has no such property'); - assert_own_property(window[inherit_exception], "prototype", - 'should inherit from ' + inherit_exception + ', but that object has no "prototype" property'); - assert_equals(Object.getPrototypeOf(window[this.name].prototype), - window[inherit_exception].prototype, - 'prototype of ' + this.name + '.prototype is not ' + inherit_exception + '.prototype'); - - // "The class string of an exception interface prototype object is the - // concatenation of the exception’s identifier and the string - // “Prototype”." - assert_class_string(window[this.name].prototype, this.name + "Prototype", - "class string of " + this.name + ".prototype"); - // TODO: Test String(), based on ES definition of - // Error.prototype.toString? - }.bind(this), this.name + " exception: existence and properties of exception interface prototype object"); - - test(function() - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", - 'interface "' + this.name + '" does not have own property "prototype"'); - - // "There must be a property named “name” on the exception interface - // prototype object with attributes { [[Writable]]: true, - // [[Enumerable]]: false, [[Configurable]]: true } and whose value is - // the identifier of the exception." - assert_own_property(window[this.name].prototype, "name", - 'prototype object does not have own property "name"'); - var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "name"); - assert_false("get" in desc, this.name + ".prototype.name has getter"); - assert_false("set" in desc, this.name + ".prototype.name has setter"); - assert_true(desc.writable, this.name + ".prototype.name is not writable"); - assert_false(desc.enumerable, this.name + ".prototype.name is enumerable"); - assert_true(desc.configurable, this.name + ".prototype.name is not configurable"); - assert_equals(desc.value, this.name, this.name + ".prototype.name has incorrect value"); - }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"name\" property"); - - test(function() - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", - 'interface "' + this.name + '" does not have own property "prototype"'); - - // "If the [NoInterfaceObject] extended attribute was not specified on - // the exception, then there must also be a property named - // “constructor” on the exception interface prototype object with - // attributes { [[Writable]]: true, [[Enumerable]]: false, - // [[Configurable]]: true } and whose value is a reference to the - // exception interface object for the exception." - assert_own_property(window[this.name].prototype, "constructor", - this.name + '.prototype does not have own property "constructor"'); - var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "constructor"); - assert_false("get" in desc, this.name + ".prototype.constructor has getter"); - assert_false("set" in desc, this.name + ".prototype.constructor has setter"); - assert_true(desc.writable, this.name + ".prototype.constructor is not writable"); - assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable"); - assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable"); - assert_equals(window[this.name].prototype.constructor, window[this.name], - this.name + '.prototype.constructor is not the same object as ' + this.name); - }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"constructor\" property"); -}; - -//@} -IdlException.prototype.test_members = function() -//@{ -{ - for (var i = 0; i < this.members.length; i++) - { - var member = this.members[i]; - if (member.untested) - { - continue; - } - if (member.type == "const" && member.name != "prototype") - { - test(function() - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - - // "For each constant defined on the exception, there must be a - // corresponding property on the exception interface object, if - // it exists, if the identifier of the constant is not - // “prototype”." - assert_own_property(window[this.name], member.name); - // "The value of the property is the ECMAScript value that is - // equivalent to the constant’s IDL value, according to the - // rules in section 4.2 above." - assert_equals(window[this.name][member.name], constValue(member.value), - "property has wrong value"); - // "The property has attributes { [[Writable]]: false, - // [[Enumerable]]: true, [[Configurable]]: false }." - var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name); - assert_false("get" in desc, "property has getter"); - assert_false("set" in desc, "property has setter"); - assert_false(desc.writable, "property is writable"); - assert_true(desc.enumerable, "property is not enumerable"); - assert_false(desc.configurable, "property is configurable"); - }.bind(this), this.name + " exception: constant " + member.name + " on exception interface object"); - // "In addition, a property with the same characteristics must - // exist on the exception interface prototype object." - test(function() - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", - 'exception "' + this.name + '" does not have own property "prototype"'); - - assert_own_property(window[this.name].prototype, member.name); - assert_equals(window[this.name].prototype[member.name], constValue(member.value), - "property has wrong value"); - var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, member.name); - assert_false("get" in desc, "property has getter"); - assert_false("set" in desc, "property has setter"); - assert_false(desc.writable, "property is writable"); - assert_true(desc.enumerable, "property is not enumerable"); - assert_false(desc.configurable, "property is configurable"); - }.bind(this), this.name + " exception: constant " + member.name + " on exception interface prototype object"); - } - else if (member.type == "field") - { - test(function() - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", - 'exception "' + this.name + '" does not have own property "prototype"'); - - // "For each exception field, there must be a corresponding - // property on the exception interface prototype object, whose - // characteristics are as follows: - // "The name of the property is the identifier of the exception - // field." - assert_own_property(window[this.name].prototype, member.name); - // "The property has attributes { [[Get]]: G, [[Enumerable]]: - // true, [[Configurable]]: true }, where G is the exception - // field getter, defined below." - var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, member.name); - assert_false("value" in desc, "property descriptor has value but is supposed to be accessor"); - assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor'); - // TODO: ES5 doesn't seem to say whether desc should have a - // .set property. - assert_true(desc.enumerable, "property is not enumerable"); - assert_true(desc.configurable, "property is not configurable"); - // "The exception field getter is a Function object whose - // behavior when invoked is as follows:" - assert_equals(typeof desc.get, "function", "typeof getter"); - // "The value of the Function object’s “length” property is the - // Number value 0." - // This test is before the TypeError tests so that it's easiest - // to see that Firefox 11a1 only fails one assert in this test. - assert_equals(desc.get.length, 0, "getter length"); - // "Let O be the result of calling ToObject on the this value. - // "If O is not a platform object representing an exception for - // the exception on which the exception field was declared, - // then throw a TypeError." - // TODO: Test on a platform object representing an exception. - assert_throws(new TypeError(), function() - { - window[this.name].prototype[member.name]; - }.bind(this), "getting property on prototype object must throw TypeError"); - assert_throws(new TypeError(), function() - { - desc.get.call({}); - }.bind(this), "calling getter on wrong object type must throw TypeError"); - }.bind(this), this.name + " exception: field " + member.name + " on exception interface prototype object"); - } - } -}; - -//@} -IdlException.prototype.test_object = function(desc) -//@{ -{ - var obj, exception = null; - try - { - obj = eval(desc); - } - catch(e) - { - exception = e; - } - - test(function() - { - assert_equals(exception, null, "Unexpected exception when evaluating object"); - assert_equals(typeof obj, "object", "wrong typeof object"); - - // We can't easily test that its prototype is correct if there's no - // interface object, or the object is from a different global - // environment (not instanceof Object). TODO: test in this case that - // its prototype at least looks correct, even if we can't test that - // it's actually correct. - if (!this.has_extended_attribute("NoInterfaceObject") - && (typeof obj != "object" || obj instanceof Object)) - { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", - 'exception "' + this.name + '" does not have own property "prototype"'); - - // "The value of the internal [[Prototype]] property of the - // exception object must be the exception interface prototype - // object from the global environment the exception object is - // associated with." - assert_equals(Object.getPrototypeOf(obj), - window[this.name].prototype, - desc + "'s prototype is not " + this.name + ".prototype"); - } - - // "The class string of the exception object must be the identifier of - // the exception." - assert_class_string(obj, this.name, "class string of " + desc); - // Stringifier is not defined for DOMExceptions, because message isn't - // defined. - }.bind(this), this.name + " must be represented by " + desc); - - for (var i = 0; i < this.members.length; i++) - { - var member = this.members[i]; - test(function() - { - assert_equals(exception, null, "Unexpected exception when evaluating object"); - assert_equals(typeof obj, "object", "wrong typeof object"); - assert_inherits(obj, member.name); - if (member.type == "const") - { - assert_equals(obj[member.name], constValue(member.value)); - } - if (member.type == "field") - { - this.array.assert_type_is(obj[member.name], member.idlType); - } - }.bind(this), this.name + " exception: " + desc + ' must inherit property "' + member.name + '" with the proper type'); - } -}; -//@} - -/// IdlInterface /// -function IdlInterface(obj) { IdlExceptionOrInterface.call(this, obj); } -IdlInterface.prototype = Object.create(IdlExceptionOrInterface.prototype); -IdlInterface.prototype.is_callback = function() -//@{ -{ - return this.has_extended_attribute("Callback"); -}; -//@} - -IdlInterface.prototype.has_constants = function() -//@{ -{ - return this.members.some(function(member) { - return member.type === "const"; - }); -}; //@} IdlInterface.prototype.test_self = function() //@{ { test(function() { - // This function tests WebIDL as of 2012-11-28. + // This function tests WebIDL as of 2015-01-13. + // TODO: Consider [Exposed]. - // "For every interface that: + // "For every interface that is exposed in a given ECMAScript global + // environment and: // * is a callback interface that has constants declared on it, or // * is a non-callback interface that is not declared with the // [NoInterfaceObject] extended attribute, // a corresponding property MUST exist on the ECMAScript global object. // The name of the property is the identifier of the interface, and its // value is an object called the interface object. // The property has the attributes { [[Writable]]: true, // [[Enumerable]]: false, [[Configurable]]: true }." if (this.is_callback() && !this.has_constants()) { return; } // TODO: Should we test here that the property is actually writable // etc., or trust getOwnPropertyDescriptor? - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - var desc = Object.getOwnPropertyDescriptor(window, this.name); - assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter"); - assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter"); - assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable"); - assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable"); - assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable"); + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + var desc = Object.getOwnPropertyDescriptor(self, this.name); + assert_false("get" in desc, "self's property " + format_value(this.name) + " has getter"); + assert_false("set" in desc, "self's property " + format_value(this.name) + " has setter"); + assert_true(desc.writable, "self's property " + format_value(this.name) + " is not writable"); + assert_false(desc.enumerable, "self's property " + format_value(this.name) + " is enumerable"); + assert_true(desc.configurable, "self's property " + format_value(this.name) + " is not configurable"); if (this.is_callback()) { // "The internal [[Prototype]] property of an interface object for // a callback interface MUST be the Object.prototype object." - assert_equals(Object.getPrototypeOf(window[this.name]), Object.prototype, - "prototype of window's property " + format_value(this.name) + " is not Object.prototype"); + assert_equals(Object.getPrototypeOf(self[this.name]), Object.prototype, + "prototype of self's property " + format_value(this.name) + " is not Object.prototype"); return; } // "The interface object for a given non-callback interface is a // function object." // "If an object is defined to be a function object, then it has // characteristics as follows:" - // "* Its [[Prototype]] internal property is the Function prototype - // object." - assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype, - "prototype of window's property " + format_value(this.name) + " is not Function.prototype"); + // Its [[Prototype]] internal property is otherwise specified (see + // below). // "* Its [[Get]] internal property is set as described in ECMA-262 - // section 15.3.5.4." + // section 9.1.8." // Not much to test for this. // "* Its [[Construct]] internal property is set as described in - // ECMA-262 section 13.2.2." + // ECMA-262 section 19.2.2.3." // Tested below if no constructor is defined. TODO: test constructors // if defined. - // "* Its [[HasInstance]] internal property is set as described in - // ECMA-262 section 15.3.5.3, unless otherwise specified." + // "* Its @@hasInstance property is set as described in ECMA-262 + // section 19.2.3.8, unless otherwise specified." // TODO - // "* Its [[NativeBrand]] internal property is “Function”." - // String() returns something implementation-dependent, because it calls - // Function#toString. - assert_class_string(window[this.name], "Function", "class string of " + this.name); + // ES6 (rev 30) 19.1.3.6: + // "Else, if O has a [[Call]] internal method, then let builtinTag be + // "Function"." + assert_class_string(self[this.name], "Function", "class string of " + this.name); + + // "The [[Prototype]] internal property of an interface object for a + // non-callback interface is determined as follows:" + var prototype = Object.getPrototypeOf(self[this.name]); + if (this.base) { + // "* If the interface inherits from some other interface, the + // value of [[Prototype]] is the interface object for that other + // interface." + var has_interface_object = + !this.array + .members[this.base] + .has_extended_attribute("NoInterfaceObject"); + if (has_interface_object) { + assert_own_property(self, this.base, + 'should inherit from ' + this.base + + ', but self has no such property'); + assert_equals(prototype, self[this.base], + 'prototype of ' + this.name + ' is not ' + + this.base); + } + } else { + // "If the interface doesn't inherit from any other interface, the + // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262], + // section 6.1.7.4)." + assert_equals(prototype, Function.prototype, + "prototype of self's property " + format_value(this.name) + " is not Function.prototype"); + } if (!this.has_extended_attribute("Constructor")) { // "The internal [[Call]] method of the interface object behaves as // follows . . . // // "If I was not declared with a [Constructor] extended attribute, // then throw a TypeError." assert_throws(new TypeError(), function() { - window[this.name](); + self[this.name](); }.bind(this), "interface object didn't throw TypeError when called as a function"); assert_throws(new TypeError(), function() { - new window[this.name](); + new self[this.name](); }.bind(this), "interface object didn't throw TypeError when called as a constructor"); } }.bind(this), this.name + " interface: existence and properties of interface object"); if (!this.is_callback()) { test(function() { // This function tests WebIDL as of 2014-10-25. // https://heycam.github.io/webidl/#es-interface-call - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); // "Interface objects for non-callback interfaces MUST have a // property named “length” with attributes { [[Writable]]: false, // [[Enumerable]]: false, [[Configurable]]: true } whose value is // a Number." - assert_own_property(window[this.name], "length"); - var desc = Object.getOwnPropertyDescriptor(window[this.name], "length"); + assert_own_property(self[this.name], "length"); + var desc = Object.getOwnPropertyDescriptor(self[this.name], "length"); assert_false("get" in desc, this.name + ".length has getter"); assert_false("set" in desc, this.name + ".length has setter"); assert_false(desc.writable, this.name + ".length is writable"); assert_false(desc.enumerable, this.name + ".length is enumerable"); assert_true(desc.configurable, this.name + ".length is not configurable"); var constructors = this.extAttrs .filter(function(attr) { return attr.name == "Constructor"; }); - var expected_length; - if (!constructors.length) { - // "If the [Constructor] extended attribute, does not appear on - // the interface definition, then the value is 0." - expected_length = 0; - } else { - // "Otherwise, the value is determined as follows: . . . - // "Return the length of the shortest argument list of the - // entries in S." - expected_length = constructors.map(function(attr) { - return attr.arguments ? attr.arguments.filter(function(arg) { - return !arg.optional; - }).length : 0; - }) - .reduce(function(m, n) { return Math.min(m, n); }); - } - assert_equals(window[this.name].length, expected_length, "wrong value for " + this.name + ".length"); + var expected_length = minOverloadLength(constructors); + assert_equals(self[this.name].length, expected_length, "wrong value for " + this.name + ".length"); }.bind(this), this.name + " interface object length"); } // TODO: Test named constructors if I find any interfaces that have them. test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + // This function tests WebIDL as of 2015-01-21. + // https://heycam.github.io/webidl/#interface-object - if (this.has_extended_attribute("Callback")) { - assert_false("prototype" in window[this.name], + if (this.is_callback() && !this.has_constants()) { + return; + } + + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + + if (this.is_callback()) { + assert_false("prototype" in self[this.name], this.name + ' should not have a "prototype" property'); return; } - // "The interface object must also have a property named “prototype” - // with attributes { [[Writable]]: false, [[Enumerable]]: false, - // [[Configurable]]: false } whose value is an object called the - // interface prototype object. This object has properties that - // correspond to the attributes and operations defined on the - // interface, and is described in more detail in section 4.5.3 below." - assert_own_property(window[this.name], "prototype", + // "An interface object for a non-callback interface must have a + // property named “prototype” with attributes { [[Writable]]: false, + // [[Enumerable]]: false, [[Configurable]]: false } whose value is an + // object called the interface prototype object. This object has + // properties that correspond to the regular attributes and regular + // operations defined on the interface, and is described in more detail + // in section 4.5.4 below." + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); - var desc = Object.getOwnPropertyDescriptor(window[this.name], "prototype"); + var desc = Object.getOwnPropertyDescriptor(self[this.name], "prototype"); assert_false("get" in desc, this.name + ".prototype has getter"); assert_false("set" in desc, this.name + ".prototype has setter"); assert_false(desc.writable, this.name + ".prototype is writable"); assert_false(desc.enumerable, this.name + ".prototype is enumerable"); assert_false(desc.configurable, this.name + ".prototype is configurable"); // Next, test that the [[Prototype]] of the interface prototype object // is correct. (This is made somewhat difficult by the existence of // [NoInterfaceObject].) // TODO: Aryeh thinks there's at least other place in this file where // we try to figure out if an interface prototype object is // correct. Consolidate that code. // "The interface prototype object for a given interface A must have an - // internal [[Prototype]] property whose value is as follows: - // "If A is not declared to inherit from another interface, then the - // value of the internal [[Prototype]] property of A is the Array - // prototype object ([ECMA-262], section 15.4.4) if the interface was - // declared with ArrayClass, or the Object prototype object otherwise + // internal [[Prototype]] property whose value is returned from the + // following steps: + // "If A is declared with the [Global] or [PrimaryGlobal] extended + // attribute, and A supports named properties, then return the named + // properties object for A, as defined in section 4.5.5 below. + // "Otherwise, if A is declared to inherit from another interface, then + // return the interface prototype object for the inherited interface. + // "Otherwise, if A is declared with the [ArrayClass] extended + // attribute, then return %ArrayPrototype% ([ECMA-262], section + // 6.1.7.4). + // "Otherwise, return %ObjectPrototype% ([ECMA-262], section 6.1.7.4). // ([ECMA-262], section 15.2.4). - // "Otherwise, A does inherit from another interface. The value of the - // internal [[Prototype]] property of A is the interface prototype - // object for the inherited interface." - var inherit_interface, inherit_interface_has_interface_object; - if (this.base) { - inherit_interface = this.base; - inherit_interface_has_interface_object = - !this.array - .members[inherit_interface] - .has_extended_attribute("NoInterfaceObject"); - } else if (this.has_extended_attribute('ArrayClass')) { - inherit_interface = 'Array'; - inherit_interface_has_interface_object = true; + if (this.name === "Window") { + assert_class_string(Object.getPrototypeOf(self[this.name].prototype), + 'WindowProperties', + 'Class name for prototype of Window' + + '.prototype is not "WindowProperties"'); } else { - inherit_interface = 'Object'; - inherit_interface_has_interface_object = true; - } - if (inherit_interface_has_interface_object) { - assert_own_property(window, inherit_interface, - 'should inherit from ' + inherit_interface + ', but window has no such property'); - assert_own_property(window[inherit_interface], 'prototype', - 'should inherit from ' + inherit_interface + ', but that object has no "prototype" property'); - assert_equals(Object.getPrototypeOf(window[this.name].prototype), - window[inherit_interface].prototype, - 'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype'); - } else { - // We can't test that we get the correct object, because this is the - // only way to get our hands on it. We only test that its class - // string, at least, is correct. - assert_class_string(Object.getPrototypeOf(window[this.name].prototype), - inherit_interface + 'Prototype', - 'Class name for prototype of ' + this.name + - '.prototype is not "' + inherit_interface + 'Prototype"'); + var inherit_interface, inherit_interface_has_interface_object; + if (this.base) { + inherit_interface = this.base; + inherit_interface_has_interface_object = + !this.array + .members[inherit_interface] + .has_extended_attribute("NoInterfaceObject"); + } else if (this.has_extended_attribute('ArrayClass')) { + inherit_interface = 'Array'; + inherit_interface_has_interface_object = true; + } else { + inherit_interface = 'Object'; + inherit_interface_has_interface_object = true; + } + if (inherit_interface_has_interface_object) { + assert_own_property(self, inherit_interface, + 'should inherit from ' + inherit_interface + ', but self has no such property'); + assert_own_property(self[inherit_interface], 'prototype', + 'should inherit from ' + inherit_interface + ', but that object has no "prototype" property'); + assert_equals(Object.getPrototypeOf(self[this.name].prototype), + self[inherit_interface].prototype, + 'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype'); + } else { + // We can't test that we get the correct object, because this is the + // only way to get our hands on it. We only test that its class + // string, at least, is correct. + assert_class_string(Object.getPrototypeOf(self[this.name].prototype), + inherit_interface + 'Prototype', + 'Class name for prototype of ' + this.name + + '.prototype is not "' + inherit_interface + 'Prototype"'); + } } // "The class string of an interface prototype object is the // concatenation of the interface’s identifier and the string // “Prototype”." - assert_class_string(window[this.name].prototype, this.name + "Prototype", + assert_class_string(self[this.name].prototype, this.name + "Prototype", "class string of " + this.name + ".prototype"); // String() should end up calling {}.toString if nothing defines a // stringifier. if (!this.has_stringifier()) { - assert_equals(String(window[this.name].prototype), "[object " + this.name + "Prototype]", + assert_equals(String(self[this.name].prototype), "[object " + this.name + "Prototype]", "String(" + this.name + ".prototype)"); } }.bind(this), this.name + " interface: existence and properties of interface prototype object"); test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + if (this.is_callback() && !this.has_constants()) { + return; + } - if (this.has_extended_attribute("Callback")) { - assert_false("prototype" in window[this.name], + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + + if (this.is_callback()) { + assert_false("prototype" in self[this.name], this.name + ' should not have a "prototype" property'); return; } - assert_own_property(window[this.name], "prototype", + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); // "If the [NoInterfaceObject] extended attribute was not specified on // the interface, then the interface prototype object must also have a // property named “constructor” with attributes { [[Writable]]: true, // [[Enumerable]]: false, [[Configurable]]: true } whose value is a // reference to the interface object for the interface." - assert_own_property(window[this.name].prototype, "constructor", + assert_own_property(self[this.name].prototype, "constructor", this.name + '.prototype does not have own property "constructor"'); - var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "constructor"); + var desc = Object.getOwnPropertyDescriptor(self[this.name].prototype, "constructor"); assert_false("get" in desc, this.name + ".prototype.constructor has getter"); assert_false("set" in desc, this.name + ".prototype.constructor has setter"); assert_true(desc.writable, this.name + ".prototype.constructor is not writable"); assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable"); assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable"); - assert_equals(window[this.name].prototype.constructor, window[this.name], + assert_equals(self[this.name].prototype.constructor, self[this.name], this.name + '.prototype.constructor is not the same object as ' + this.name); }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property'); }; //@} IdlInterface.prototype.test_member_const = function(member) //@{ { test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + if (this.is_callback() && !this.has_constants()) { + return; + } + + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); // "For each constant defined on an interface A, there must be // a corresponding property on the interface object, if it // exists." - assert_own_property(window[this.name], member.name); + assert_own_property(self[this.name], member.name); // "The value of the property is that which is obtained by // converting the constant’s IDL value to an ECMAScript // value." - assert_equals(window[this.name][member.name], constValue(member.value), + assert_equals(self[this.name][member.name], constValue(member.value), "property has wrong value"); // "The property has attributes { [[Writable]]: false, // [[Enumerable]]: true, [[Configurable]]: false }." - var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name); + var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name); assert_false("get" in desc, "property has getter"); assert_false("set" in desc, "property has setter"); assert_false(desc.writable, "property is writable"); assert_true(desc.enumerable, "property is not enumerable"); assert_false(desc.configurable, "property is configurable"); }.bind(this), this.name + " interface: constant " + member.name + " on interface object"); // "In addition, a property with the same characteristics must // exist on the interface prototype object." test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + if (this.is_callback() && !this.has_constants()) { + return; + } - if (this.has_extended_attribute("Callback")) { - assert_false("prototype" in window[this.name], + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + + if (this.is_callback()) { + assert_false("prototype" in self[this.name], this.name + ' should not have a "prototype" property'); return; } - assert_own_property(window[this.name], "prototype", + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); - assert_own_property(window[this.name].prototype, member.name); - assert_equals(window[this.name].prototype[member.name], constValue(member.value), + assert_own_property(self[this.name].prototype, member.name); + assert_equals(self[this.name].prototype[member.name], constValue(member.value), "property has wrong value"); - var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name); + var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name); assert_false("get" in desc, "property has getter"); assert_false("set" in desc, "property has setter"); assert_false(desc.writable, "property is writable"); assert_true(desc.enumerable, "property is not enumerable"); assert_false(desc.configurable, "property is configurable"); }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object"); }; //@} IdlInterface.prototype.test_member_attribute = function(member) //@{ { test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", + if (this.is_callback() && !this.has_constants()) { + return; + } + + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); if (member["static"]) { - assert_own_property(window[this.name], member.name, + assert_own_property(self[this.name], member.name, "The interface object must have a property " + format_value(member.name)); - } - else - { - assert_true(member.name in window[this.name].prototype, + } else if (this.is_global()) { + assert_own_property(self, member.name, + "The global object must have a property " + + format_value(member.name)); + assert_false(member.name in self[this.name].prototype, + "The prototype object must not have a property " + + format_value(member.name)); + + // Try/catch around the get here, since it can legitimately throw. + // If it does, we obviously can't check for equality with direct + // invocation of the getter. + var gotValue; + var propVal; + try { + propVal = self[member.name]; + gotValue = true; + } catch (e) { + gotValue = false; + } + if (gotValue) { + var getter = Object.getOwnPropertyDescriptor(self, member.name).get; + assert_equals(typeof(getter), "function", + format_value(member.name) + " must have a getter"); + assert_equals(propVal, getter.call(undefined), + "Gets on a global should not require an explicit this"); + } + this.do_interface_attribute_asserts(self, member); + } else { + assert_true(member.name in self[this.name].prototype, "The prototype object must have a property " + format_value(member.name)); if (!member.has_extended_attribute("LenientThis")) { assert_throws(new TypeError(), function() { - window[this.name].prototype[member.name]; + self[this.name].prototype[member.name]; }.bind(this), "getting property on prototype object must throw TypeError"); } else { - assert_equals(window[this.name].prototype[member.name], undefined, + assert_equals(self[this.name].prototype[member.name], undefined, "getting property on prototype object must return undefined"); } - do_interface_attribute_asserts(window[this.name].prototype, member); + this.do_interface_attribute_asserts(self[this.name].prototype, member); } }.bind(this), this.name + " interface: attribute " + member.name); }; //@} IdlInterface.prototype.test_member_operation = function(member) //@{ { test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + if (this.is_callback() && !this.has_constants()) { + return; + } - if (this.has_extended_attribute("Callback")) { - assert_false("prototype" in window[this.name], + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + + if (this.is_callback()) { + assert_false("prototype" in self[this.name], this.name + ' should not have a "prototype" property'); return; } - assert_own_property(window[this.name], "prototype", + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); // "For each unique identifier of an operation defined on the // interface, there must be a corresponding property on the // interface prototype object (if it is a regular operation) or // the interface object (if it is a static operation), unless // the effective overload set for that identifier and operation // and with an argument count of 0 (for the ECMAScript language // binding) has no entries." // - var prototypeOrInterfaceObject; + var memberHolderObject; if (member["static"]) { - assert_own_property(window[this.name], member.name, - "interface prototype object missing static operation"); - prototypeOrInterfaceObject = window[this.name]; - } - else - { - assert_own_property(window[this.name].prototype, member.name, + assert_own_property(self[this.name], member.name, + "interface object missing static operation"); + memberHolderObject = self[this.name]; + } else if (this.is_global()) { + assert_own_property(self, member.name, + "global object missing non-static operation"); + memberHolderObject = self; + } else { + assert_own_property(self[this.name].prototype, member.name, "interface prototype object missing non-static operation"); - prototypeOrInterfaceObject = window[this.name].prototype; + memberHolderObject = self[this.name].prototype; } - var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name); - // "The property has attributes { [[Writable]]: true, - // [[Enumerable]]: true, [[Configurable]]: true }." - assert_false("get" in desc, "property has getter"); - assert_false("set" in desc, "property has setter"); - assert_true(desc.writable, "property is not writable"); - assert_true(desc.enumerable, "property is not enumerable"); - assert_true(desc.configurable, "property is not configurable"); - // "The value of the property is a Function object whose - // behavior is as follows . . ." - assert_equals(typeof prototypeOrInterfaceObject[member.name], "function", - "property must be a function"); - // "The value of the Function object’s “length” property is - // a Number determined as follows: - // ". . . - // "Return the length of the shortest argument list of the - // entries in S." - // - // TODO: Doesn't handle overloading or variadic arguments. - assert_equals(prototypeOrInterfaceObject[member.name].length, - member.arguments.filter(function(arg) { - return !arg.optional; - }).length, - "property has wrong .length"); + this.do_member_operation_asserts(memberHolderObject, member); + }.bind(this), this.name + " interface: operation " + member.name + + "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) + + ")"); +}; - // Make some suitable arguments - var args = member.arguments.map(function(arg) { - return create_suitable_object(arg.idlType); - }); +//@} +IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject, member) +//@{ +{ + var operationUnforgeable = member.isUnforgeable; + var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name); + // "The property has attributes { [[Writable]]: B, + // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the + // operation is unforgeable on the interface, and true otherwise". + assert_false("get" in desc, "property has getter"); + assert_false("set" in desc, "property has setter"); + assert_equals(desc.writable, !operationUnforgeable, + "property should be writable if and only if not unforgeable"); + assert_true(desc.enumerable, "property is not enumerable"); + assert_equals(desc.configurable, !operationUnforgeable, + "property should be configurable if and only if not unforgeable"); + // "The value of the property is a Function object whose + // behavior is as follows . . ." + assert_equals(typeof memberHolderObject[member.name], "function", + "property must be a function"); + // "The value of the Function object’s “length” property is + // a Number determined as follows: + // ". . . + // "Return the length of the shortest argument list of the + // entries in S." + assert_equals(memberHolderObject[member.name].length, + minOverloadLength(this.members.filter(function(m) { + return m.type == "operation" && m.name == member.name; + })), + "property has wrong .length"); - // "Let O be a value determined as follows: - // ". . . - // "Otherwise, throw a TypeError." - // This should be hit if the operation is not static, there is - // no [ImplicitThis] attribute, and the this value is null. - // - // TODO: We currently ignore the [ImplicitThis] case. - if (!member["static"]) { + // Make some suitable arguments + var args = member.arguments.map(function(arg) { + return create_suitable_object(arg.idlType); + }); + + // "Let O be a value determined as follows: + // ". . . + // "Otherwise, throw a TypeError." + // This should be hit if the operation is not static, there is + // no [ImplicitThis] attribute, and the this value is null. + // + // TODO: We currently ignore the [ImplicitThis] case. Except we manually + // check for globals, since otherwise we'll invoke window.close(). And we + // have to skip this test for anything that on the proto chain of "self", + // since that does in fact have implicit-this behavior. + if (!member["static"]) { + if (!this.is_global() && + memberHolderObject[member.name] != self[member.name]) + { assert_throws(new TypeError(), function() { - window[this.name].prototype[member.name].apply(null, args); + memberHolderObject[member.name].apply(null, args); }, "calling operation with this = null didn't throw TypeError"); } // ". . . If O is not null and is also not a platform object // that implements interface I, throw a TypeError." // // TODO: Test a platform object that implements some other // interface. (Have to be sure to get inheritance right.) assert_throws(new TypeError(), function() { - window[this.name].prototype[member.name].apply({}, args); + memberHolderObject[member.name].apply({}, args); }, "calling operation with this = {} didn't throw TypeError"); - }.bind(this), this.name + " interface: operation " + member.name + - "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) + - ")"); -}; + } +} //@} IdlInterface.prototype.test_member_stringifier = function(member) //@{ { test(function() { - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); + if (this.is_callback() && !this.has_constants()) { + return; + } - if (this.has_extended_attribute("Callback")) { - assert_false("prototype" in window[this.name], + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + + if (this.is_callback()) { + assert_false("prototype" in self[this.name], this.name + ' should not have a "prototype" property'); return; } - assert_own_property(window[this.name], "prototype", + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); // ". . . the property exists on the interface prototype object." - var interfacePrototypeObject = window[this.name].prototype; - assert_own_property(window[this.name].prototype, "toString", + var interfacePrototypeObject = self[this.name].prototype; + assert_own_property(self[this.name].prototype, "toString", "interface prototype object missing non-static operation"); + var stringifierUnforgeable = member.isUnforgeable; var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString"); // "The property has attributes { [[Writable]]: B, // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the // stringifier is unforgeable on the interface, and true otherwise." assert_false("get" in desc, "property has getter"); assert_false("set" in desc, "property has setter"); - assert_true(desc.writable, "property is not writable"); + assert_equals(desc.writable, !stringifierUnforgeable, + "property should be writable if and only if not unforgeable"); assert_true(desc.enumerable, "property is not enumerable"); - assert_true(desc.configurable, "property is not configurable"); + assert_equals(desc.configurable, !stringifierUnforgeable, + "property should be configurable if and only if not unforgeable"); // "The value of the property is a Function object, which behaves as // follows . . ." assert_equals(typeof interfacePrototypeObject.toString, "function", "property must be a function"); // "The value of the Function object’s “length” property is the Number // value 0." assert_equals(interfacePrototypeObject.toString.length, 0, "property has wrong .length"); // "Let O be the result of calling ToObject on the this value." assert_throws(new TypeError(), function() { - window[this.name].prototype.toString.apply(null, []); + self[this.name].prototype.toString.apply(null, []); }, "calling stringifier with this = null didn't throw TypeError"); // "If O is not an object that implements the interface on which the // stringifier was declared, then throw a TypeError." // // TODO: Test a platform object that implements some other // interface. (Have to be sure to get inheritance right.) assert_throws(new TypeError(), function() { - window[this.name].prototype.toString.apply({}, []); + self[this.name].prototype.toString.apply({}, []); }, "calling stringifier with this = {} didn't throw TypeError"); }.bind(this), this.name + " interface: stringifier"); }; //@} IdlInterface.prototype.test_members = function() //@{ { @@ -1448,26 +1239,32 @@ IdlInterface.prototype.test_members = fu switch (member.type) { case "const": this.test_member_const(member); break; case "attribute": // For unforgeable attributes, we do the checks in // test_interface_of instead. - if (!member.has_extended_attribute("Unforgeable")) { + if (!member.isUnforgeable) + { this.test_member_attribute(member); } break; case "operation": // TODO: Need to correctly handle multiple operations with the same // identifier. + // For unforgeable operations, we do the checks in + // test_interface_of instead. if (member.name) { - this.test_member_operation(member); + if (!member.isUnforgeable) + { + this.test_member_operation(member); + } } else if (member.stringifier) { this.test_member_stringifier(member); } break; default: // TODO: check more member types. break; @@ -1526,27 +1323,27 @@ IdlInterface.prototype.test_primary_inte // least looks correct, even if we can't test that it's actually correct. if (!this.has_extended_attribute("NoInterfaceObject") && (typeof obj != expected_typeof || obj instanceof Object)) { test(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - assert_own_property(window, this.name, - "window does not have own property " + format_value(this.name)); - assert_own_property(window[this.name], "prototype", + assert_own_property(self, this.name, + "self does not have own property " + format_value(this.name)); + assert_own_property(self[this.name], "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); // "The value of the internal [[Prototype]] property of the // platform object is the interface prototype object of the primary // interface from the platform object’s associated global // environment." assert_equals(Object.getPrototypeOf(obj), - window[this.name].prototype, + self[this.name].prototype, desc + "'s prototype is not " + this.name + ".prototype"); }.bind(this), this.name + " must be primary interface of " + desc); } // "The class string of a platform object that implements one or more // interfaces must be the identifier of the primary interface of the // platform object." test(function() @@ -1566,36 +1363,54 @@ IdlInterface.prototype.test_interface_of //@{ { // TODO: Indexed and named properties, more checks on interface members this.already_tested = true; for (var i = 0; i < this.members.length; i++) { var member = this.members[i]; - if (member.has_extended_attribute("Unforgeable")) + if (member.type == "attribute" && member.isUnforgeable) { test(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - do_interface_attribute_asserts(obj, member); + this.do_interface_attribute_asserts(obj, member); + }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"'); + } + else if (member.type == "operation" && + member.name && + member.isUnforgeable) + { + test(function() + { + assert_equals(exception, null, "Unexpected exception when evaluating object"); + assert_equals(typeof obj, expected_typeof, "wrong typeof object"); + assert_own_property(obj, member.name, + "Doesn't have the unforgeable operation property"); + this.do_member_operation_asserts(obj, member); }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"'); } else if ((member.type == "const" || member.type == "attribute" || member.type == "operation") && member.name) { test(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); if (!member["static"]) { - assert_inherits(obj, member.name); + if (!this.is_global()) { + assert_inherits(obj, member.name); + } else { + assert_own_property(obj, member.name); + } + if (member.type == "const") { assert_equals(obj[member.name], constValue(member.value)); } if (member.type == "attribute") { // Attributes are accessor properties, so they might // legitimately throw an exception rather than returning @@ -1626,29 +1441,32 @@ IdlInterface.prototype.test_interface_of // TODO: Test passing arguments of the wrong type. if (member.type == "operation" && member.name && member.arguments.length) { test(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); if (!member["static"]) { - assert_inherits(obj, member.name); + if (!this.is_global() && !member.isUnforgeable) { + assert_inherits(obj, member.name); + } else { + assert_own_property(obj, member.name); + } } else { assert_false(member.name in obj); } + + var minLength = minOverloadLength(this.members.filter(function(m) { + return m.type == "operation" && m.name == member.name; + })); var args = []; - for (var i = 0; i < member.arguments.length; i++) - { - if (member.arguments[i].optional) - { - break; - } + for (var i = 0; i < minLength; i++) { assert_throws(new TypeError(), function() { obj[member.name].apply(obj, args); }.bind(this), "Called with " + i + " arguments"); args.push(create_suitable_object(member.arguments[i].idlType)); } }.bind(this), this.name + " interface: calling " + member.name + @@ -1668,92 +1486,117 @@ IdlInterface.prototype.has_stringifier = if (this.base && this.array.members[this.base].has_stringifier()) { return true; } return false; }; //@} -function do_interface_attribute_asserts(obj, member) +IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member) //@{ { - // "For each attribute defined on the interface, there must exist a - // corresponding property. If the attribute was declared with the - // [Unforgeable] extended attribute, then the property exists on every - // object that implements the interface. Otherwise, it exists on the - // interface’s interface prototype object." - // - // This is called by test_self() with the prototype as obj, and by - // test_interface_of() with the object as obj. + // This function tests WebIDL as of 2015-01-27. + // TODO: Consider [Exposed]. + + // This is called by test_member_attribute() with the prototype as obj if + // it is not a global, and the global otherwise, and by test_interface_of() + // with the object as obj. + + // "For each exposed attribute of the interface, whether it was declared on + // the interface itself or one of its consequential interfaces, there MUST + // exist a corresponding property. The characteristics of this property are + // as follows:" + + // "The name of the property is the identifier of the attribute." assert_own_property(obj, member.name); // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]: // true, [[Configurable]]: configurable }, where: // "configurable is false if the attribute was declared with the // [Unforgeable] extended attribute and true otherwise; // "G is the attribute getter, defined below; and // "S is the attribute setter, also defined below." var desc = Object.getOwnPropertyDescriptor(obj, member.name); assert_false("value" in desc, 'property descriptor has value but is supposed to be accessor'); assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor'); assert_true(desc.enumerable, "property is not enumerable"); - if (member.has_extended_attribute("Unforgeable")) + if (member.isUnforgeable) { assert_false(desc.configurable, "[Unforgeable] property must not be configurable"); } else { assert_true(desc.configurable, "property must be configurable"); } + // "The attribute getter is a Function object whose behavior when invoked - // is as follows: - // "... + // is as follows:" + assert_equals(typeof desc.get, "function", "getter must be Function"); + + // "If the attribute is a regular attribute, then:" + if (!member["static"]) { + // "If O is not a platform object that implements I, then: + // "If the attribute was specified with the [LenientThis] extended + // attribute, then return undefined. + // "Otherwise, throw a TypeError." + if (!member.has_extended_attribute("LenientThis")) { + assert_throws(new TypeError(), function() { + desc.get.call({}); + }.bind(this), "calling getter on wrong object type must throw TypeError"); + } else { + assert_equals(desc.get.call({}), undefined, + "calling getter on wrong object type must return undefined"); + } + } + // "The value of the Function object’s “length” property is the Number // value 0." - assert_equals(typeof desc.get, "function", "getter must be Function"); assert_equals(desc.get.length, 0, "getter length must be 0"); - if (!member.has_extended_attribute("LenientThis")) { - assert_throws(new TypeError(), function() { - desc.get.call({}); - }.bind(this), "calling getter on wrong object type must throw TypeError"); - } else { - assert_equals(desc.get.call({}), undefined, - "calling getter on wrong object type must return undefined"); - } + // TODO: Test calling setter on the interface prototype (should throw // TypeError in most cases). - // - // "The attribute setter is undefined if the attribute is declared readonly - // and has neither a [PutForwards] nor a [Replaceable] extended attribute - // declared on it. Otherwise, it is a Function object whose behavior when - // invoked is as follows: - // "... - // "The value of the Function object’s “length” property is the Number - // value 1." if (member.readonly && !member.has_extended_attribute("PutForwards") && !member.has_extended_attribute("Replaceable")) { + // "The attribute setter is undefined if the attribute is declared + // readonly and has neither a [PutForwards] nor a [Replaceable] + // extended attribute declared on it." assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes"); } else { + // "Otherwise, it is a Function object whose behavior when + // invoked is as follows:" assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes"); + + // "If the attribute is a regular attribute, then:" + if (!member["static"]) { + // "If /validThis/ is false and the attribute was not specified + // with the [LenientThis] extended attribute, then throw a + // TypeError." + // "If the attribute is declared with a [Replaceable] extended + // attribute, then: ..." + // "If validThis is false, then return." + if (!member.has_extended_attribute("LenientThis")) { + assert_throws(new TypeError(), function() { + desc.set.call({}); + }.bind(this), "calling setter on wrong object type must throw TypeError"); + } else { + assert_equals(desc.set.call({}), undefined, + "calling setter on wrong object type must return undefined"); + } + } + + // "The value of the Function object’s “length” property is the Number + // value 1." assert_equals(desc.set.length, 1, "setter length must be 1"); - if (!member.has_extended_attribute("LenientThis")) { - assert_throws(new TypeError(), function() { - desc.set.call({}); - }.bind(this), "calling setter on wrong object type must throw TypeError"); - } else { - assert_equals(desc.set.call({}), undefined, - "calling setter on wrong object type must return undefined"); - } } } //@} /// IdlInterfaceMember /// function IdlInterfaceMember(obj) //@{ { @@ -1765,16 +1608,18 @@ function IdlInterfaceMember(obj) for (var k in obj) { this[k] = obj[k]; } if (!("extAttrs" in this)) { this.extAttrs = []; } + + this.isUnforgeable = this.has_extended_attribute("Unforgeable"); } //@} IdlInterfaceMember.prototype = Object.create(IdlObject.prototype); /// Internal helper functions /// function create_suitable_object(type) //@{
--- a/dom/imptests/moz.build +++ b/dom/imptests/moz.build @@ -27,10 +27,9 @@ MOCHITEST_MANIFESTS += [ 'failures/html/html/semantics/forms/the-select-element/mochitest.ini', 'failures/html/html/semantics/scripting-1/the-script-element/mochitest.ini', 'failures/html/html/semantics/tabular-data/the-table-element/mochitest.ini', 'failures/html/html/webappapis/atob/mochitest.ini', 'failures/html/js/builtins/mochitest.ini', 'failures/html/microdata/microdata-dom-api/mochitest.ini', 'failures/html/typedarrays/mochitest.ini', 'failures/webapps/WebStorage/tests/submissions/Infraware/mochitest.ini', - 'failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini', ]
--- a/dom/imptests/testharness.css +++ b/dom/imptests/testharness.css @@ -9,21 +9,16 @@ html { } #log .error, #log .error a { color: white; background: red; } -#log pre { - border: 1px solid black; - padding: 1em; -} - section#summary { margin-bottom:1em; } table#results { border-collapse:collapse; table-layout:fixed; width:100%;
--- a/dom/imptests/testharness.js +++ b/dom/imptests/testharness.js @@ -17,17 +17,18 @@ policies and contribution forms [3]. var debug = false; // default timeout is 10 seconds, test can override if needed var settings = { output:true, harness_timeout:{ "normal":10000, "long":60000 }, - test_timeout:null + test_timeout:null, + message_events: ["start", "test_state", "result", "completion"] }; var xhtml_ns = "http://www.w3.org/1999/xhtml"; /* * TestEnvironment is an abstraction for the environment in which the test * harness is used. Each implementation of a test environment has to provide * the following interface: @@ -59,30 +60,73 @@ policies and contribution forms [3]. * apisample11.html and apisample12.html. */ function WindowTestEnvironment() { this.name_counter = 0; this.window_cache = null; this.output_handler = null; this.all_loaded = false; var this_obj = this; + this.message_events = []; + + this.message_functions = { + start: [add_start_callback, remove_start_callback, + function (properties) { + this_obj._dispatch("start_callback", [properties], + {type: "start", properties: properties}); + }], + + test_state: [add_test_state_callback, remove_test_state_callback, + function(test) { + this_obj._dispatch("test_state_callback", [test], + {type: "test_state", + test: test.structured_clone()}); + }], + result: [add_result_callback, remove_result_callback, + function (test) { + this_obj.output_handler.show_status(); + this_obj._dispatch("result_callback", [test], + {type: "result", + test: test.structured_clone()}); + }], + completion: [add_completion_callback, remove_completion_callback, + function (tests, harness_status) { + var cloned_tests = map(tests, function(test) { + return test.structured_clone(); + }); + this_obj._dispatch("completion_callback", [tests, harness_status], + {type: "complete", + tests: cloned_tests, + status: harness_status.structured_clone()}); + }] + } + on_event(window, 'load', function() { this_obj.all_loaded = true; }); } WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) { this._forEach_windows( - function(w, is_same_origin) { - if (is_same_origin && selector in w) { + function(w, same_origin) { + if (same_origin) { try { - w[selector].apply(undefined, callback_args); - } catch (e) { - if (debug) { - throw e; + var has_selector = selector in w; + } catch(e) { + // If document.domain was set at some point same_origin can be + // wrong and the above will fail. + has_selector = false; + } + if (has_selector) { + try { + w[selector].apply(undefined, callback_args); + } catch (e) { + if (debug) { + throw e; + } } } } if (supports_post_message(w) && w !== self) { w.postMessage(message_arg, "*"); } }); }; @@ -136,51 +180,63 @@ policies and contribution forms [3]. }); }; WindowTestEnvironment.prototype.on_tests_ready = function() { var output = new Output(); this.output_handler = output; var this_obj = this; + add_start_callback(function (properties) { this_obj.output_handler.init(properties); - this_obj._dispatch("start_callback", [properties], - { type: "start", properties: properties }); }); + add_test_state_callback(function(test) { this_obj.output_handler.show_status(); - this_obj._dispatch("test_state_callback", [test], - { type: "test_state", test: test.structured_clone() }); }); + add_result_callback(function (test) { this_obj.output_handler.show_status(); - this_obj._dispatch("result_callback", [test], - { type: "result", test: test.structured_clone() }); }); + add_completion_callback(function (tests, harness_status) { this_obj.output_handler.show_results(tests, harness_status); - var cloned_tests = map(tests, function(test) { return test.structured_clone(); }); - this_obj._dispatch("completion_callback", [tests, harness_status], - { type: "complete", tests: cloned_tests, - status: harness_status.structured_clone() }); }); + this.setup_messages(settings.message_events); }; + WindowTestEnvironment.prototype.setup_messages = function(new_events) { + var this_obj = this; + forEach(settings.message_events, function(x) { + var current_dispatch = this_obj.message_events.indexOf(x) !== -1; + var new_dispatch = new_events.indexOf(x) !== -1; + if (!current_dispatch && new_dispatch) { + this_obj.message_functions[x][0](this_obj.message_functions[x][2]); + } else if (current_dispatch && !new_dispatch) { + this_obj.message_functions[x][1](this_obj.message_functions[x][2]); + } + }); + this.message_events = new_events; + } + WindowTestEnvironment.prototype.next_default_test_name = function() { //Don't use document.title to work around an Opera bug in XHTML documents var title = document.getElementsByTagName("title")[0]; var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled"; var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; this.name_counter++; return prefix + suffix; }; WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) { this.output_handler.setup(properties); + if (properties.hasOwnProperty("message_events")) { + this.setup_messages(properties.message_events); + } }; WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) { on_event(window, 'load', callback); }; WindowTestEnvironment.prototype.test_timeout = function() { var metas = document.getElementsByTagName("meta"); @@ -354,18 +410,30 @@ policies and contribution forms [3]. function ServiceWorkerTestEnvironment() { WorkerTestEnvironment.call(this); this.all_loaded = false; this.on_loaded_callback = null; var this_obj = this; self.addEventListener("message", function(event) { if (event.data.type && event.data.type === "connect") { - this_obj._add_message_port(event.ports[0]); - event.ports[0].start(); + if (event.ports && event.ports[0]) { + // If a MessageChannel was passed, then use it to + // send results back to the main window. This + // allows the tests to work even if the browser + // does not fully support MessageEvent.source in + // ServiceWorkers yet. + this_obj._add_message_port(event.ports[0]); + event.ports[0].start(); + } else { + // If there is no MessageChannel, then attempt to + // use the MessageEvent.source to send results + // back to the main window. + this_obj._add_message_port(event.source); + } } }); // The oninstall event is received after the service worker script and // all imported scripts have been fetched and executed. It's the // equivalent of an onload event for a document. All tests should have // been added by the time this event is received, thus it's not // necessary to wait until the onactivate event. @@ -459,16 +527,22 @@ policies and contribution forms [3]. if (value instanceof AssertionError) { throw value; } assert(false, "promise_test", null, "Unhandled rejection with value: ${value}", {value:value}); })); } + function promise_rejects(test, expected, promise) { + return promise.then(test.unreached_func("Should have rejected.")).catch(function(e) { + assert_throws(expected, function() { throw e }); + }); + } + /** * This constructor helper allows DOM events to be handled using Promises, * which can make it a lot easier to test a very specific series of events, * including ensuring that unexpected events are not fired at any point. */ function EventWatcher(test, watchedNode, eventTypes) { if (typeof eventTypes == 'string') { @@ -574,16 +648,17 @@ policies and contribution forms [3]. function on_event(object, event, callback) { object.addEventListener(event, callback, false); } expose(test, 'test'); expose(async_test, 'async_test'); expose(promise_test, 'promise_test'); + expose(promise_rejects, 'promise_rejects'); expose(generate_tests, 'generate_tests'); expose(setup, 'setup'); expose(done, 'done'); expose(on_event, 'on_event'); /* * Return a string truncated to the given length, with ... added at the end * if it was longer. @@ -837,17 +912,17 @@ policies and contribution forms [3]. assert(actual.length === expected.length, "assert_array_equals", description, "lengths differ, expected ${expected} got ${actual}", {expected:expected.length, actual:actual.length}); for (var i = 0; i < actual.length; i++) { assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), "assert_array_equals", description, - "property ${i}, property expected to be $expected but was $actual", + "property ${i}, property expected to be ${expected} but was ${actual}", {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", actual:actual.hasOwnProperty(i) ? "present" : "missing"}); assert(same_value(expected[i], actual[i]), "assert_array_equals", description, "property ${i}, expected ${expected} but got ${actual}", {i:i, expected:expected[i], actual:actual[i]}); } } @@ -928,17 +1003,17 @@ policies and contribution forms [3]. * Test if a primitive number is less than or equal to another */ assert(typeof actual === "number", "assert_less_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); assert(actual <= expected, - "assert_less_than", description, + "assert_less_than_equal", description, "expected a number less than or equal to ${expected} but got ${actual}", {expected:expected, actual:actual}); } expose(assert_less_than_equal, "assert_less_than_equal"); function assert_greater_than_equal(actual, expected, description) { /* @@ -1120,32 +1195,38 @@ policies and contribution forms [3]. NetworkError: 19, AbortError: 20, URLMismatchError: 21, QuotaExceededError: 22, TimeoutError: 23, InvalidNodeTypeError: 24, DataCloneError: 25, + EncodingError: 0, + NotReadableError: 0, UnknownError: 0, ConstraintError: 0, DataError: 0, TransactionInactiveError: 0, ReadOnlyError: 0, - VersionError: 0 + VersionError: 0, + OperationError: 0, }; if (!(name in name_code_map)) { throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()'); } var required_props = { code: name_code_map[name] }; if (required_props.code === 0 || - ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) { + (typeof e == "object" && + "name" in e && + e.name !== e.name.toUpperCase() && + e.name !== "DOMException")) { // New style exception: also test the name property. required_props.name = name; } //We'd like to test that e instanceof the appropriate interface, //but we can't, because we don't know what window it was created //in. It might be an instanceof the appropriate interface on some //unknown other window. TODO: Work around this somehow? @@ -1209,16 +1290,17 @@ policies and contribution forms [3]. var timeout = properties.timeout ? properties.timeout : settings.test_timeout; if (timeout !== null) { this.timeout_length = timeout * tests.timeout_multiplier; } else { this.timeout_length = null; } this.message = null; + this.stack = null; this.steps = []; this.cleanup_callbacks = []; tests.push(this); } @@ -1245,16 +1327,17 @@ policies and contribution forms [3]. msg = msg ? String(msg) : msg; this._structured_clone = merge({ name:String(this.name), properties:merge({}, this.properties), }, Test.statuses); } this._structured_clone.status = this.status; this._structured_clone.message = this.message; + this._structured_clone.stack = this.stack; this._structured_clone.index = this.index; return this._structured_clone; }; Test.prototype.step = function(func, this_obj) { if (this.phase > this.phases.STARTED) { return; @@ -1277,25 +1360,20 @@ policies and contribution forms [3]. } try { return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)); } catch (e) { if (this.phase >= this.phases.HAS_RESULT) { return; } - var message = (typeof e === "object" && e !== null) ? e.message : e; - if (typeof e.stack != "undefined" && typeof e.message == "string") { - //Try to make it more informative for some exceptions, at least - //in Gecko and WebKit. This results in a stack dump instead of - //just errors like "Cannot read property 'parentNode' of null" - //or "root is null". Makes it a lot longer, of course. - message += "(stack: " + e.stack + ")"; - } - this.set_status(this.FAIL, message); + var message = String((typeof e === "object" && e !== null) ? e.message : e); + var stack = e.stack ? e.stack : null; + + this.set_status(this.FAIL, message, stack); this.phase = this.phases.HAS_RESULT; this.done(); } }; Test.prototype.step_func = function(func, this_obj) { var test_this = this; @@ -1351,20 +1429,21 @@ policies and contribution forms [3]. var this_obj = this; this.timeout_id = setTimeout(function() { this_obj.timeout(); }, this.timeout_length); } }; - Test.prototype.set_status = function(status, message) + Test.prototype.set_status = function(status, message, stack) { this.status = status; this.message = message; + this.stack = stack ? stack : null; }; Test.prototype.timeout = function() { this.timeout_id = null; this.set_status(this.TIMEOUT, "Test timed out"); this.phase = this.phases.HAS_RESULT; this.done(); @@ -1411,32 +1490,33 @@ policies and contribution forms [3]. this.phase = this.phases.INITIAL; this.update_state_from(clone); tests.push(this); } RemoteTest.prototype.structured_clone = function() { var clone = {}; Object.keys(this).forEach( - function(key) { + (function(key) { if (typeof(this[key]) === "object") { clone[key] = merge({}, this[key]); } else { clone[key] = this[key]; } - }); + }).bind(this)); clone.phases = merge({}, this.phases); return clone; }; RemoteTest.prototype.cleanup = function() {}; RemoteTest.prototype.phases = Test.prototype.phases; RemoteTest.prototype.update_state_from = function(clone) { this.status = clone.status; this.message = clone.message; + this.stack = clone.stack; if (this.phase === this.phases.INITIAL) { this.phase = this.phases.STARTED; } }; RemoteTest.prototype.done = function() { this.phase = this.phases.COMPLETE; } @@ -1450,25 +1530,34 @@ policies and contribution forms [3]. this.tests = new Array(); var this_obj = this; worker.onerror = function(error) { this_obj.worker_error(error); }; var message_port; if (is_service_worker(worker)) { - // The ServiceWorker's implicit MessagePort is currently not - // reliably accessible from the ServiceWorkerGlobalScope due to - // Blink setting MessageEvent.source to null for messages sent via - // ServiceWorker.postMessage(). Until that's resolved, create an - // explicit MessageChannel and pass one end to the worker. - var message_channel = new MessageChannel(); - message_port = message_channel.port1; - message_port.start(); - worker.postMessage({type: "connect"}, [message_channel.port2]); + if (window.MessageChannel) { + // The ServiceWorker's implicit MessagePort is currently not + // reliably accessible from the ServiceWorkerGlobalScope due to + // Blink setting MessageEvent.source to null for messages sent + // via ServiceWorker.postMessage(). Until that's resolved, + // create an explicit MessageChannel and pass one end to the + // worker. + var message_channel = new MessageChannel(); + message_port = message_channel.port1; + message_port.start(); + worker.postMessage({type: "connect"}, [message_channel.port2]); + } else { + // If MessageChannel is not available, then try the + // ServiceWorker.postMessage() approach using MessageEvent.source + // on the other end. + message_port = navigator.serviceWorker; + worker.postMessage({type: "connect"}); + } } else if (is_shared_worker(worker)) { message_port = worker.port; } else { message_port = worker; } // Keeping a reference to the worker until worker_done() is seen // prevents the Worker object and its MessageChannel from going away @@ -1486,17 +1575,18 @@ policies and contribution forms [3]. RemoteWorker.prototype.worker_error = function(error) { var message = error.message || String(error); var filename = (error.filename ? " " + error.filename: ""); // FIXME: Display worker error states separately from main document // error state. this.worker_done({ status: { status: tests.status.ERROR, - message: "Error in worker" + filename + ": " + message + message: "Error in worker" + filename + ": " + message, + stack: error.stack } }); error.preventDefault(); }; RemoteWorker.prototype.test_state = function(data) { var remote_test = this.tests[data.test.index]; if (!remote_test) { @@ -1514,16 +1604,17 @@ policies and contribution forms [3]. tests.result(remote_test); }; RemoteWorker.prototype.worker_done = function(data) { if (tests.status.status === null && data.status.status !== data.status.OK) { tests.status.status = data.status.status; tests.status.message = data.status.message; + tests.status.stack = data.status.stack; } this.running = false; this.worker = null; if (tests.all_done()) { tests.complete(); } }; @@ -1536,16 +1627,17 @@ policies and contribution forms [3]. /* * Harness */ function TestsStatus() { this.status = null; this.message = null; + this.stack = null; } TestsStatus.statuses = { OK:0, ERROR:1, TIMEOUT:2 }; @@ -1553,17 +1645,18 @@ policies and contribution forms [3]. TestsStatus.prototype.structured_clone = function() { if (!this._structured_clone) { var msg = this.message; msg = msg ? String(msg) : msg; this._structured_clone = merge({ status:this.status, - message:msg + message:msg, + stack:this.stack }, TestsStatus.statuses); } return this._structured_clone; }; function Tests() { this.tests = []; @@ -1643,16 +1736,17 @@ policies and contribution forms [3]. } if (func) { try { func(); } catch (e) { this.status.status = this.status.ERROR; this.status.message = String(e); + this.status.stack = e.stack ? e.stack : null; } } this.set_timeout(); }; Tests.prototype.set_file_is_test = function() { if (this.tests.length > 0) { throw new Error("Tried to set file as test after creating a test"); @@ -1806,31 +1900,52 @@ policies and contribution forms [3]. function add_start_callback(callback) { tests.start_callbacks.push(callback); } function add_test_state_callback(callback) { tests.test_state_callbacks.push(callback); } - function add_result_callback(callback) - { + function add_result_callback(callback) { tests.test_done_callbacks.push(callback); } - function add_completion_callback(callback) - { - tests.all_done_callbacks.push(callback); + function add_completion_callback(callback) { + tests.all_done_callbacks.push(callback); } expose(add_start_callback, 'add_start_callback'); expose(add_test_state_callback, 'add_test_state_callback'); expose(add_result_callback, 'add_result_callback'); expose(add_completion_callback, 'add_completion_callback'); + function remove(array, item) { + var index = array.indexOf(item); + if (index > -1) { + array.splice(index, 1); + } + } + + function remove_start_callback(callback) { + remove(tests.start_callbacks, callback); + } + + function remove_test_state_callback(callback) { + remove(tests.test_state_callbacks, callback); + } + + function remove_result_callback(callback) { + remove(tests.test_done_callbacks, callback); + } + + function remove_completion_callback(callback) { + remove(tests.all_done_callbacks, callback); + } + /* * Output listener */ function Output() { this.output_document = document; this.output_node = null; this.enabled = settings.output; @@ -2000,16 +2115,19 @@ policies and contribution forms [3]. ["span", {"class":status_class(status)}, status ], ] ]]; if (harness_status.status === harness_status.ERROR) { rv[0].push(["pre", {}, harness_status.message]); + if (harness_status.stack) { + rv[0].push(["pre", {}, harness_status.stack]); + } } return rv; }, ["p", {}, "Found ${num_tests} tests"], function() { var rv = [["div", {}]]; var i = 0; while (status_text.hasOwnProperty(i)) { @@ -2097,16 +2215,19 @@ policies and contribution forms [3]. escape_html(status_class(status_text[tests[i].status])) + '"><td>' + escape_html(status_text[tests[i].status]) + "</td><td>" + escape_html(tests[i].name) + "</td><td>" + (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") + escape_html(tests[i].message ? tests[i].message : " ") + + (tests[i].stack ? "<pre>" + + escape_html(tests[i].stack) + + "</pre>": "") + "</td></tr>"; } html += "</tbody></table>"; try { log.lastChild.innerHTML = html; } catch (e) { log.appendChild(document.createElementNS(xhtml_ns, "p")) .textContent = "Setting innerHTML for the log threw an exception."; @@ -2292,21 +2413,44 @@ policies and contribution forms [3]. error, substitutions); throw new AssertionError(msg); } } function AssertionError(message) { this.message = message; + this.stack = this.get_stack(); } - AssertionError.prototype.toString = function() { - return this.message; - }; + AssertionError.prototype = Object.create(Error.prototype); + + AssertionError.prototype.get_stack = function() { + var stack = new Error().stack; + if (!stack) { + try { + throw new Error(); + } catch (e) { + stack = e.stack; + } + } + var lines = stack.split("\n"); + var rv = []; + var re = /\/resources\/testharness\.js/; + var i = 0; + // Fire remove any preamble that doesn't match the regexp + while (!re.test(lines[i])) { + i++ + } + // Then remove top frames in testharness.js itself + while (re.test(lines[i])) { + i++ + } + return lines.slice(i).join("\n"); + } function make_message(function_name, description, error, substitutions) { for (var p in substitutions) { if (substitutions.hasOwnProperty(p)) { substitutions[p] = format_value(substitutions[p]); } } @@ -2435,23 +2579,23 @@ policies and contribution forms [3]. var tests = new Tests(); addEventListener("error", function(e) { if (tests.file_is_test) { var test = tests.tests[0]; if (test.phase >= test.phases.HAS_RESULT) { return; } - var message = e.message; - test.set_status(test.FAIL, message); + test.set_status(test.FAIL, e.message, e.stack); test.phase = test.phases.HAS_RESULT; test.done(); done(); } else if (!tests.allow_uncaught_exception) { tests.status.status = tests.status.ERROR; tests.status.message = e.message; + tests.status.stack = e.stack; } }); test_environment.on_tests_ready(); })(); // vim: set expandtab shiftwidth=4 tabstop=4:
--- a/dom/indexedDB/ActorsChild.cpp +++ b/dom/indexedDB/ActorsChild.cpp @@ -1257,24 +1257,36 @@ BackgroundFactoryRequestChild::HandleRes AssertIsOnOwningThread(); mRequest->Reset(); auto databaseActor = static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild()); MOZ_ASSERT(databaseActor); - databaseActor->EnsureDOMObject(); - IDBDatabase* database = databaseActor->GetDOMObject(); - MOZ_ASSERT(database); - - ResultHelper helper(mRequest, nullptr, database); - - DispatchSuccessEvent(&helper); + if (!database) { + databaseActor->EnsureDOMObject(); + + database = databaseActor->GetDOMObject(); + MOZ_ASSERT(database); + + MOZ_ASSERT(!database->IsClosed()); + } + + if (database->IsClosed()) { + // If the database was closed already, which is only possible if we fired an + // "upgradeneeded" event, then we shouldn't fire a "success" event here. + // Instead we fire an error event with AbortErr. + DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); + } else { + ResultHelper helper(mRequest, nullptr, database); + + DispatchSuccessEvent(&helper); + } databaseActor->ReleaseDOMObject(); return true; } bool BackgroundFactoryRequestChild::HandleResponse(
--- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -5338,21 +5338,20 @@ protected: const OptionalKeyRange& aKeyRange); static nsresult UpdateIndexValues(DatabaseConnection* aConnection, const int64_t aObjectStoreId, const Key& aObjectStoreKey, const FallibleTArray<IndexDataValue>& aIndexValues); -#ifdef DEBUG - static bool + static nsresult ObjectStoreHasIndexes(DatabaseConnection* aConnection, - const int64_t aObjectStoreId); -#endif + const int64_t aObjectStoreId, + bool* aHasIndexes); private: template <typename T> static nsresult GetStructuredCloneReadInfoFromSource(T* aSource, uint32_t aDataIndex, uint32_t aFileIdsIndex, FileManager* aFileManager, @@ -6936,27 +6935,25 @@ private: class DeleteObjectStoreOp final : public VersionChangeTransactionOp { friend class VersionChangeTransaction; const nsRefPtr<FullObjectStoreMetadata> mMetadata; const bool mIsLastObjectStore; - const bool mObjectStoreHasIndexes; private: // Only created by VersionChangeTransaction. DeleteObjectStoreOp(VersionChangeTransaction* aTransaction, FullObjectStoreMetadata* const aMetadata, const bool aIsLastObjectStore) : VersionChangeTransactionOp(aTransaction) , mMetadata(aMetadata) , mIsLastObjectStore(aIsLastObjectStore) - , mObjectStoreHasIndexes(aMetadata->HasLiveIndexes()) { MOZ_ASSERT(aMetadata->mCommonMetadata.id()); } ~DeleteObjectStoreOp() { } virtual nsresult @@ -7133,16 +7130,25 @@ protected: : TransactionDatabaseOperationBase(aTransaction) , mResponseSent(false) { } virtual ~NormalTransactionOp() { } + // An overload of DatabaseOperationBase's function that can avoid doing extra + // work on non-versionchange transactions. + static nsresult + ObjectStoreHasIndexes(NormalTransactionOp* aOp, + DatabaseConnection* aConnection, + const int64_t aObjectStoreId, + const bool aMayHaveIndexes, + bool* aHasIndexes); + // Subclasses use this override to set the IPDL response value. virtual void GetResponse(RequestResponse& aResponse) = 0; private: virtual nsresult SendSuccessResult() override; @@ -7174,17 +7180,17 @@ class ObjectStoreAddOrPutRequestOp final nsRefPtr<FileManager> mFileManager; Key mResponse; const nsCString mGroup; const nsCString mOrigin; const PersistenceType mPersistenceType; const bool mOverwrite; - const bool mObjectStoreHasIndexes; + const bool mObjectStoreMayHaveIndexes; private: // Only created by TransactionBase. ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction, const RequestParams& aParams); ~ObjectStoreAddOrPutRequestOp() { } @@ -7292,17 +7298,17 @@ private: class ObjectStoreDeleteRequestOp final : public NormalTransactionOp { friend class TransactionBase; const ObjectStoreDeleteParams mParams; ObjectStoreDeleteResponse mResponse; - const bool mObjectStoreHasIndexes; + const bool mObjectStoreMayHaveIndexes; private: ObjectStoreDeleteRequestOp(TransactionBase* aTransaction, const ObjectStoreDeleteParams& aParams); ~ObjectStoreDeleteRequestOp() { } @@ -7318,17 +7324,17 @@ private: class ObjectStoreClearRequestOp final : public NormalTransactionOp { friend class TransactionBase; const ObjectStoreClearParams mParams; ObjectStoreClearResponse mResponse; - const bool mObjectStoreHasIndexes; + const bool mObjectStoreMayHaveIndexes; private: ObjectStoreClearRequestOp(TransactionBase* aTransaction, const ObjectStoreClearParams& aParams); ~ObjectStoreClearRequestOp() { } @@ -13055,22 +13061,22 @@ TransactionBase::GetMetadataForObjectSto MOZ_ASSERT(aObjectStoreId); if (!aObjectStoreId) { return nullptr; } nsRefPtr<FullObjectStoreMetadata> metadata; if (!mDatabase->Metadata()->mObjectStores.Get(aObjectStoreId, - getter_AddRefs(metadata))) { + getter_AddRefs(metadata)) || + metadata->mDeleted) { return nullptr; } MOZ_ASSERT(metadata->mCommonMetadata.id() == aObjectStoreId); - MOZ_ASSERT(!metadata->mDeleted); return metadata.forget(); } already_AddRefed<FullIndexMetadata> TransactionBase::GetMetadataForIndexId( FullObjectStoreMetadata* const aObjectStoreMetadata, int64_t aIndexId) const @@ -13078,22 +13084,22 @@ TransactionBase::GetMetadataForIndexId( AssertIsOnBackgroundThread(); MOZ_ASSERT(aIndexId); if (!aIndexId) { return nullptr; } nsRefPtr<FullIndexMetadata> metadata; - if (!aObjectStoreMetadata->mIndexes.Get(aIndexId, getter_AddRefs(metadata))) { + if (!aObjectStoreMetadata->mIndexes.Get(aIndexId, getter_AddRefs(metadata)) || + metadata->mDeleted) { return nullptr; } MOZ_ASSERT(metadata->mCommonMetadata.id() == aIndexId); - MOZ_ASSERT(!metadata->mDeleted); return metadata.forget(); } void TransactionBase::NoteModifiedAutoIncrementObjectStore( FullObjectStoreMetadata* aMetadata) { @@ -18058,18 +18064,26 @@ nsresult DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes( DatabaseConnection* aConnection, const int64_t aObjectStoreId, const OptionalKeyRange& aKeyRange) { MOZ_ASSERT(aConnection); aConnection->AssertIsOnConnectionThread(); MOZ_ASSERT(aObjectStoreId); - MOZ_ASSERT(ObjectStoreHasIndexes(aConnection, aObjectStoreId), - "Don't use this slow method if there are no indexes!"); + +#ifdef DEBUG + { + bool hasIndexes = false; + MOZ_ASSERT(NS_SUCCEEDED( + ObjectStoreHasIndexes(aConnection, aObjectStoreId, &hasIndexes))); + MOZ_ASSERT(hasIndexes, + "Don't use this slow method if there are no indexes!"); + } +#endif PROFILER_LABEL("IndexedDB", "DatabaseOperationBase::" "DeleteObjectStoreDataTableRowsWithIndexes", js::ProfileEntry::Category::STORAGE); const bool singleRowOnly = aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange && @@ -18267,47 +18281,54 @@ DatabaseOperationBase::UpdateIndexValues rv = updateStmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } -#ifdef DEBUG - // static -bool +nsresult DatabaseOperationBase::ObjectStoreHasIndexes(DatabaseConnection* aConnection, - const int64_t aObjectStoreId) + const int64_t aObjectStoreId, + bool* aHasIndexes) { MOZ_ASSERT(aConnection); aConnection->AssertIsOnConnectionThread(); MOZ_ASSERT(aObjectStoreId); + MOZ_ASSERT(aHasIndexes); DatabaseConnection::CachedStatement stmt; - MOZ_ALWAYS_TRUE(NS_SUCCEEDED( - aConnection->GetCachedStatement(NS_LITERAL_CSTRING( - "SELECT id " - "FROM object_store_index " - "WHERE object_store_id = :object_store_id;"), - &stmt))); - - MOZ_ALWAYS_TRUE(NS_SUCCEEDED( - stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"), - aObjectStoreId))); + nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING( + "SELECT id " + "FROM object_store_index " + "WHERE object_store_id = :object_store_id " + "LIMIT 1;"), + &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"), + aObjectStoreId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } bool hasResult; - MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult))); - - return hasResult; -} - -#endif // DEBUG + rv = stmt->ExecuteStep(&hasResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aHasIndexes = hasResult; + return NS_OK; +} NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase, nsRunnable, mozIStorageProgressHandler) NS_IMETHODIMP DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection, bool* _retval) @@ -19664,17 +19685,28 @@ OpenDatabaseOp::BeginVersionChange() mState = State_WaitingForOtherDatabasesToClose; return NS_OK; } void OpenDatabaseOp::NoteDatabaseClosed(Database* aDatabase) { AssertIsOnOwningThread(); - MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose); + MOZ_ASSERT(aDatabase); + MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose || + mState == State_DatabaseWorkVersionChange); + + if (mState == State_DatabaseWorkVersionChange) { + MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty()); + MOZ_ASSERT(mRequestedVersion > + aDatabase->Metadata()->mCommonMetadata.version(), + "Must only be closing databases for a previous version!"); + return; + } + MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty()); bool actorDestroyed = IsActorDestroyed() || mDatabase->IsActorDestroyed(); nsresult rv; if (actorDestroyed) { IDB_REPORT_INTERNAL_ERR(); rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; @@ -21630,21 +21662,16 @@ DeleteObjectStoreOp::DoDatabaseWork(Data foundOtherObjectStore = true; } } MOZ_ASSERT_IF(mIsLastObjectStore, foundThisObjectStore && !foundOtherObjectStore); MOZ_ASSERT_IF(!mIsLastObjectStore, foundThisObjectStore && foundOtherObjectStore); - - // Make sure |hasIndexes| is telling the truth. - MOZ_ASSERT(mObjectStoreHasIndexes == - ObjectStoreHasIndexes(aConnection, - mMetadata->mCommonMetadata.id())); } #endif DatabaseConnection::AutoSavepoint autoSave; nsresult rv = autoSave.Start(Transaction()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -21707,17 +21734,25 @@ DeleteObjectStoreOp::DoDatabaseWork(Data return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { - if (mObjectStoreHasIndexes) { + bool hasIndexes; + rv = ObjectStoreHasIndexes(aConnection, + mMetadata->mCommonMetadata.id(), + &hasIndexes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (hasIndexes) { rv = DeleteObjectStoreDataTableRowsWithIndexes( aConnection, mMetadata->mCommonMetadata.id(), void_t()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -22785,16 +22820,58 @@ DeleteIndexOp::DoDatabaseWork(DatabaseCo rv = autoSave.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } +// static +nsresult +NormalTransactionOp::ObjectStoreHasIndexes(NormalTransactionOp* aOp, + DatabaseConnection* aConnection, + const int64_t aObjectStoreId, + const bool aMayHaveIndexes, + bool* aHasIndexes) +{ + MOZ_ASSERT(aOp); + MOZ_ASSERT(aConnection); + aConnection->AssertIsOnConnectionThread(); + MOZ_ASSERT(aObjectStoreId); + MOZ_ASSERT(aHasIndexes); + + bool hasIndexes; + if (aOp->Transaction()->GetMode() == IDBTransaction::VERSION_CHANGE && + aMayHaveIndexes) { + // If this is a version change transaction then mObjectStoreMayHaveIndexes + // could be wrong (e.g. if a unique index failed to be created due to a + // constraint error). We have to check on this thread by asking the database + // directly. + nsresult rv = + DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, + aObjectStoreId, + &hasIndexes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + MOZ_ASSERT(NS_SUCCEEDED( + DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, + aObjectStoreId, + &hasIndexes))); + MOZ_ASSERT(aMayHaveIndexes == hasIndexes); + + hasIndexes = aMayHaveIndexes; + } + + *aHasIndexes = hasIndexes; + return NS_OK; +} + nsresult NormalTransactionOp::SendSuccessResult() { AssertIsOnOwningThread(); if (!IsActorDestroyed()) { RequestResponse response; GetResponse(response); @@ -22861,37 +22938,48 @@ ObjectStoreAddOrPutRequestOp::ObjectStor : NormalTransactionOp(aTransaction) , mParams(aParams.type() == RequestParams::TObjectStoreAddParams ? aParams.get_ObjectStoreAddParams().commonParams() : aParams.get_ObjectStorePutParams().commonParams()) , mGroup(aTransaction->GetDatabase()->Group()) , mOrigin(aTransaction->GetDatabase()->Origin()) , mPersistenceType(aTransaction->GetDatabase()->Type()) , mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams) - , mObjectStoreHasIndexes(false) + , mObjectStoreMayHaveIndexes(false) { MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams || aParams.type() == RequestParams::TObjectStorePutParams); mMetadata = aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId()); MOZ_ASSERT(mMetadata); - const_cast<bool&>(mObjectStoreHasIndexes) = mMetadata->HasLiveIndexes(); + const_cast<bool&>(mObjectStoreMayHaveIndexes) = mMetadata->HasLiveIndexes(); } nsresult ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues( DatabaseConnection* aConnection) { AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection); MOZ_ASSERT(mOverwrite); MOZ_ASSERT(!mResponse.IsUnset()); - MOZ_ASSERT(mObjectStoreHasIndexes); + +#ifdef DEBUG + { + bool hasIndexes = false; + MOZ_ASSERT(NS_SUCCEEDED( + DatabaseOperationBase::ObjectStoreHasIndexes(aConnection, + mParams.objectStoreId(), + &hasIndexes))); + MOZ_ASSERT(hasIndexes, + "Don't use this slow method if there are no indexes!"); + } +#endif DatabaseConnection::CachedStatement indexValuesStmt; nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING( "SELECT index_data_values " "FROM object_data " "WHERE object_store_id = :object_store_id " "AND key = :key;"), &indexValuesStmt); @@ -23093,44 +23181,52 @@ ObjectStoreAddOrPutRequestOp::Init(Trans nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) { MOZ_ASSERT(aConnection); aConnection->AssertIsOnConnectionThread(); MOZ_ASSERT(aConnection->GetStorageConnection()); MOZ_ASSERT_IF(mFileManager, !mStoredFileInfos.IsEmpty()); - MOZ_ASSERT(mObjectStoreHasIndexes == - ObjectStoreHasIndexes(aConnection, mParams.objectStoreId())); PROFILER_LABEL("IndexedDB", "ObjectStoreAddOrPutRequestOp::DoDatabaseWork", js::ProfileEntry::Category::STORAGE); if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) { return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; } DatabaseConnection::AutoSavepoint autoSave; nsresult rv = autoSave.Start(Transaction()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + bool objectStoreHasIndexes; + rv = ObjectStoreHasIndexes(this, + aConnection, + mParams.objectStoreId(), + mObjectStoreMayHaveIndexes, + &objectStoreHasIndexes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // This will be the final key we use. Key& key = mResponse; key = mParams.key(); const bool keyUnset = key.IsUnset(); const int64_t osid = mParams.objectStoreId(); const KeyPath& keyPath = mMetadata->mCommonMetadata.keyPath(); // First delete old index_data_values if we're overwriting something and we // have indexes. - if (mOverwrite && !keyUnset && mObjectStoreHasIndexes) { + if (mOverwrite && !keyUnset && objectStoreHasIndexes) { rv = RemoveOldIndexDataValues(aConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // The "|| keyUnset" here is mostly a debugging tool. If a key isn't // specified we should never have a collision and so it shouldn't matter @@ -23774,47 +23870,54 @@ ObjectStoreGetAllKeysRequestOp::GetRespo } } ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp( TransactionBase* aTransaction, const ObjectStoreDeleteParams& aParams) : NormalTransactionOp(aTransaction) , mParams(aParams) - , mObjectStoreHasIndexes(false) + , mObjectStoreMayHaveIndexes(false) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aTransaction); nsRefPtr<FullObjectStoreMetadata> metadata = aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId()); MOZ_ASSERT(metadata); - const_cast<bool&>(mObjectStoreHasIndexes) = metadata->HasLiveIndexes(); + const_cast<bool&>(mObjectStoreMayHaveIndexes) = metadata->HasLiveIndexes(); } nsresult ObjectStoreDeleteRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) { MOZ_ASSERT(aConnection); aConnection->AssertIsOnConnectionThread(); - MOZ_ASSERT(mObjectStoreHasIndexes == - ObjectStoreHasIndexes(aConnection, mParams.objectStoreId())); - PROFILER_LABEL("IndexedDB", "ObjectStoreDeleteRequestOp::DoDatabaseWork", js::ProfileEntry::Category::STORAGE); DatabaseConnection::AutoSavepoint autoSave; nsresult rv = autoSave.Start(Transaction()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (mObjectStoreHasIndexes) { + bool objectStoreHasIndexes; + rv = ObjectStoreHasIndexes(this, + aConnection, + mParams.objectStoreId(), + mObjectStoreMayHaveIndexes, + &objectStoreHasIndexes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (objectStoreHasIndexes) { rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection, mParams.objectStoreId(), mParams.keyRange()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id"); @@ -23859,47 +23962,55 @@ ObjectStoreDeleteRequestOp::DoDatabaseWo return NS_OK; } ObjectStoreClearRequestOp::ObjectStoreClearRequestOp( TransactionBase* aTransaction, const ObjectStoreClearParams& aParams) : NormalTransactionOp(aTransaction) , mParams(aParams) - , mObjectStoreHasIndexes(false) + , mObjectStoreMayHaveIndexes(false) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aTransaction); nsRefPtr<FullObjectStoreMetadata> metadata = aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId()); MOZ_ASSERT(metadata); - const_cast<bool&>(mObjectStoreHasIndexes) = metadata->HasLiveIndexes(); + const_cast<bool&>(mObjectStoreMayHaveIndexes) = metadata->HasLiveIndexes(); } nsresult ObjectStoreClearRequestOp::DoDatabaseWork(DatabaseConnection* aConnection) { MOZ_ASSERT(aConnection); aConnection->AssertIsOnConnectionThread(); - MOZ_ASSERT(mObjectStoreHasIndexes == - ObjectStoreHasIndexes(aConnection, mParams.objectStoreId())); PROFILER_LABEL("IndexedDB", "ObjectStoreClearRequestOp::DoDatabaseWork", js::ProfileEntry::Category::STORAGE); DatabaseConnection::AutoSavepoint autoSave; nsresult rv = autoSave.Start(Transaction()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (mObjectStoreHasIndexes) { + bool objectStoreHasIndexes; + rv = ObjectStoreHasIndexes(this, + aConnection, + mParams.objectStoreId(), + mObjectStoreMayHaveIndexes, + &objectStoreHasIndexes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (objectStoreHasIndexes) { rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection, mParams.objectStoreId(), void_t()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { DatabaseConnection::CachedStatement stmt;
--- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -515,25 +515,27 @@ already_AddRefed<IDBObjectStore> IDBDatabase::CreateObjectStore( const nsAString& aName, const IDBObjectStoreParameters& aOptionalParameters, ErrorResult& aRv) { AssertIsOnOwningThread(); IDBTransaction* transaction = IDBTransaction::GetCurrent(); - if (!transaction || transaction->Database() != this || transaction->GetMode() != IDBTransaction::VERSION_CHANGE) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } - MOZ_ASSERT(transaction->IsOpen()); + if (!transaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return nullptr; + } KeyPath keyPath(0); if (NS_FAILED(KeyPath::Parse(aOptionalParameters.mKeyPath, &keyPath))) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores(); @@ -591,25 +593,27 @@ IDBDatabase::CreateObjectStore( } void IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) { AssertIsOnOwningThread(); IDBTransaction* transaction = IDBTransaction::GetCurrent(); - if (!transaction || transaction->Database() != this || transaction->GetMode() != IDBTransaction::VERSION_CHANGE) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return; } - MOZ_ASSERT(transaction->IsOpen()); + if (!transaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return; + } nsTArray<ObjectStoreSpec>& specArray = mSpec->objectStores(); int64_t objectStoreId = 0; for (uint32_t specCount = specArray.Length(), specIndex = 0; specIndex < specCount; specIndex++) {
--- a/dom/indexedDB/IDBIndex.cpp +++ b/dom/indexedDB/IDBIndex.cpp @@ -218,16 +218,21 @@ IDBIndex::GetKeyPath(JSContext* aCx, already_AddRefed<IDBRequest> IDBIndex::GetInternal(bool aKeyOnly, JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedMetadata) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); @@ -301,16 +306,21 @@ already_AddRefed<IDBRequest> IDBIndex::GetAllInternal(bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aKey, const Optional<uint32_t>& aLimit, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedMetadata) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); @@ -382,16 +392,21 @@ already_AddRefed<IDBRequest> IDBIndex::OpenCursorInternal(bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedMetadata) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange)); @@ -478,16 +493,21 @@ IDBIndex::OpenCursorInternal(bool aKeysO already_AddRefed<IDBRequest> IDBIndex::Count(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedMetadata) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + IDBTransaction* transaction = mObjectStore->Transaction(); if (!transaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
--- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -1155,16 +1155,21 @@ IDBObjectStore::AddOrPut(JSContext* aCx, bool aOverwrite, bool aFromCursor, ErrorResult& aRv) { AssertIsOnOwningThread(); MOZ_ASSERT(aCx); MOZ_ASSERT_IF(aFromCursor, aOverwrite); + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!mTransaction->IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; @@ -1309,16 +1314,21 @@ already_AddRefed<IDBRequest> IDBObjectStore::GetAllInternal(bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aKey, const Optional<uint32_t>& aLimit, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); if (NS_WARN_IF(aRv.Failed())) { @@ -1381,16 +1391,21 @@ IDBObjectStore::GetAllInternal(bool aKey return request.forget(); } already_AddRefed<IDBRequest> IDBObjectStore::Clear(ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!mTransaction->IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; @@ -1567,16 +1582,21 @@ IDBObjectStore::IndexNames() already_AddRefed<IDBRequest> IDBObjectStore::Get(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); if (aRv.Failed()) { @@ -1615,16 +1635,21 @@ IDBObjectStore::Get(JSContext* aCx, already_AddRefed<IDBRequest> IDBObjectStore::DeleteInternal(JSContext* aCx, JS::Handle<JS::Value> aKey, bool aFromCursor, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } if (!mTransaction->IsWriteAllowed()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); return nullptr; @@ -1708,22 +1733,25 @@ already_AddRefed<IDBIndex> IDBObjectStore::CreateIndexInternal( const nsAString& aName, const KeyPath& aKeyPath, const IDBIndexParameters& aOptionalParameters, ErrorResult& aRv) { AssertIsOnOwningThread(); - IDBTransaction* transaction = IDBTransaction::GetCurrent(); + if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE || + mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } - if (!transaction || - transaction != mTransaction || - mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE) { - aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + IDBTransaction* transaction = IDBTransaction::GetCurrent(); + if (!transaction || transaction != mTransaction) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } MOZ_ASSERT(transaction->IsOpen()); auto& indexes = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes()); for (uint32_t count = indexes.Length(), index = 0; index < count; @@ -1788,22 +1816,25 @@ IDBObjectStore::CreateIndexInternal( return index.forget(); } void IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv) { AssertIsOnOwningThread(); - IDBTransaction* transaction = IDBTransaction::GetCurrent(); + if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE || + mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return; + } - if (!transaction || - transaction != mTransaction || - mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE) { - aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + IDBTransaction* transaction = IDBTransaction::GetCurrent(); + if (!transaction || transaction != mTransaction) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return; } MOZ_ASSERT(transaction->IsOpen()); auto& metadataArray = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes()); int64_t foundId = 0; @@ -1861,16 +1892,23 @@ IDBObjectStore::DeleteIndex(const nsAStr transaction->DeleteIndex(this, foundId); } already_AddRefed<IDBRequest> IDBObjectStore::Count(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv) { + AssertIsOnOwningThread(); + + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange)); if (aRv.Failed()) { @@ -1911,16 +1949,21 @@ already_AddRefed<IDBRequest> IDBObjectStore::OpenCursorInternal(bool aKeysOnly, JSContext* aCx, JS::Handle<JS::Value> aRange, IDBCursorDirection aDirection, ErrorResult& aRv) { AssertIsOnOwningThread(); + if (mDeletedSpec) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); return nullptr; } nsRefPtr<IDBKeyRange> keyRange; aRv = IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange)); if (NS_WARN_IF(aRv.Failed())) {
--- a/dom/indexedDB/test/error_events_abort_transactions_iframe.html +++ b/dom/indexedDB/test/error_events_abort_transactions_iframe.html @@ -53,16 +53,19 @@ let request = indexedDB.open(window.location.pathname, 1); request.onsuccess = unexpectedSuccessHandler; request.onerror = grabEventAndContinueHandler; request.onupgradeneeded = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; + db.onversionchange = function(event) { + event.target.close(); + }; is(db.version, 1, "Correct version"); is(db.objectStoreNames.length, 0, "Correct objectStoreNames length"); let trans = event.target.transaction; trans.oncomplete = unexpectedSuccessHandler; trans.onabort = grabEventAndContinueHandler; @@ -99,16 +102,19 @@ is(originalRequest.transaction, null, "request.transaction should now be null"); request = indexedDB.open(window.location.pathname, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; event = yield undefined; db = event.target.result; + db.onversionchange = function(event) { + event.target.close(); + }; event.target.transaction.oncomplete = grabEventAndContinueHandler; event.target.transaction.onabort = unexpectedSuccessHandler; is(db.version, "1", "Correct version"); is(db.objectStoreNames.length, 0, "Correct objectStoreNames length"); objectStore = db.createObjectStore("foo"); @@ -148,24 +154,25 @@ is(event.type, "complete", "Got a transaction complete event"); is(db.version, "1", "Correct version"); is(db.objectStoreNames.length, 2, "Correct objectStoreNames length"); ok(db.objectStoreNames.contains("foo"), "Has correct objectStore"); ok(db.objectStoreNames.contains("bar"), "Has correct objectStore"); - db.close(); - request = indexedDB.open(window.location.pathname, 2); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; event = yield undefined; db = event.target.result; + db.onversionchange = function(event) { + event.target.close(); + }; trans = event.target.transaction; trans.oncomplete = unexpectedSuccessHandler; is(db.version, "2", "Correct version"); is(db.objectStoreNames.length, 2, "Correct objectStoreNames length"); ok(db.objectStoreNames.contains("foo"), "Has correct objectStore"); ok(db.objectStoreNames.contains("bar"), "Has correct objectStore");
--- a/dom/indexedDB/test/unit/test_add_twice_failure.js +++ b/dom/indexedDB/test/unit/test_add_twice_failure.js @@ -7,16 +7,17 @@ var testGenerator = testSteps(); function testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = request.result; ok(event.target === request, "Good event target"); let objectStore = db.createObjectStore("foo", { keyPath: null }); let key = 10; @@ -28,12 +29,15 @@ function testSteps() is(request.result, key, "Correct key"); request = objectStore.add({}, key); request.addEventListener("error", new ExpectError("ConstraintError", true)); request.onsuccess = unexpectedSuccessHandler; yield undefined; + // Wait for success. + yield undefined; + finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js +++ b/dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js @@ -24,16 +24,17 @@ function testSteps() for (let i = 0; i < objectStoreInfo.length; i++) { // Create our object stores. let info = objectStoreInfo[i]; ok(true, "1"); request = indexedDB.open(name, i + 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; event = yield undefined; let db = event.target.result; ok(true, "2"); let objectStore = info.hasOwnProperty("options") ? db.createObjectStore(info.name, info.options) : db.createObjectStore(info.name); @@ -80,15 +81,19 @@ function testSteps() request = uniqueIndex.get(END_DATA); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; event = yield undefined; ok(true, "7"); ok(obj.data, event.target.result.data, "Unique index was properly updated."); + + // Wait for success + yield undefined; + db.close(); } finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_cursors.js +++ b/dom/indexedDB/test/unit/test_cursors.js @@ -11,16 +11,17 @@ function testSteps() const keys = [1, -1, 0, 10, 2000, "q", "z", "two", "b", "a"]; const sortedKeys = [-1, 0, 1, 10, 2000, "a", "b", "q", "two", "z"]; is(keys.length, sortedKeys.length, "Good key setup"); let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; let objectStore = db.createObjectStore("autoIncrement", { autoIncrement: true }); request = objectStore.openCursor(); @@ -367,11 +368,16 @@ function testSteps() ok(storedCursor.value === undefined, "The cursor's value should be undefined."); testGenerator.next(); } } yield undefined; is(keyIndex, -1, "Saw all added items"); + // Wait for success + yield undefined; + + db.close(); + finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_event_source.js +++ b/dom/indexedDB/test/unit/test_event_source.js @@ -8,25 +8,29 @@ var testGenerator = testSteps(); function testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; const objectStoreName = "Objects"; var request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; var event = yield undefined; is(event.target.source, null, "correct event.target.source"); var db = event.target.result; var objectStore = db.createObjectStore(objectStoreName, { autoIncrement: true }); request = objectStore.add({}); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; event = yield undefined; ok(event.target.source === objectStore, "correct event.source"); + // Wait for success + yield undefined; + finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_key_requirements.js +++ b/dom/indexedDB/test/unit/test_key_requirements.js @@ -7,16 +7,17 @@ var testGenerator = testSteps(); function testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; db.addEventListener("error", function(event) { event.preventDefault(); }, false); let objectStore = db.createObjectStore("foo", { autoIncrement: true }); @@ -271,11 +272,14 @@ function testSteps() ok(true, "add with inline key and passed key threw"); } request = objectStore.delete(key2); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; event = yield undefined; + // Wait for success + yield undefined; + finishTest(); yield undefined; -} \ No newline at end of file +}
--- a/dom/indexedDB/test/unit/test_objectCursors.js +++ b/dom/indexedDB/test/unit/test_objectCursors.js @@ -22,16 +22,19 @@ function testSteps() var j = 0; for (let i in objectStores) { let request = indexedDB.open(name, ++j); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; + db.onversionchange = function(event) { + event.target.close(); + }; let objectStore = db.createObjectStore(objectStores[i].name, { keyPath: "id", autoIncrement: objectStores[i].autoIncrement }); for (let j in indexes) { objectStore.createIndex(indexes[j].name, "name", indexes[j].options); @@ -43,17 +46,16 @@ function testSteps() } request = objectStore.add(data); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; event = yield undefined; ok(event.target.result == 1 || event.target.result == 2, "Good id"); - db.close(); } executeSoon(function() { testGenerator.next(); }); yield undefined; let request = indexedDB.open(name, j); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler;
--- a/dom/indexedDB/test/unit/test_objectStore_inline_autoincrement_key_added_on_put.js +++ b/dom/indexedDB/test/unit/test_objectStore_inline_autoincrement_key_added_on_put.js @@ -7,16 +7,17 @@ var testGenerator = testSteps(); function testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; var request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; var event = yield undefined; var db = event.target.result; var test = { name: "inline key; key generator", autoIncrement: true, storedObject: {name: "Lincoln"}, @@ -40,12 +41,15 @@ function testSteps() // Sanity check! is(event.target.result.name, test.storedObject.name, "The correct object was stored."); // Ensure that the id was also stored on the object. is(event.target.result.id, id, "The object had the id stored on it."); + // Wait for success + yield undefined; + finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_objectStore_remove_values.js +++ b/dom/indexedDB/test/unit/test_objectStore_remove_values.js @@ -37,19 +37,23 @@ function testSteps() ]; for (let i = 0; i < data.length; i++) { let test = data[i]; let request = indexedDB.open(name, i+1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; + db.onversionchange = function(event) { + event.target.close(); + }; let objectStore = db.createObjectStore(test.name, { keyPath: test.keyName, autoIncrement: test.autoIncrement }); request = objectStore.add(test.storedObject, test.keyValue); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; @@ -72,15 +76,17 @@ function testSteps() // Make sure it was removed. request = objectStore.get(id); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; event = yield undefined; ok(event.target.result === undefined, "Object was deleted"); - db.close(); + + // Wait for success + yield undefined; } finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_put_get_values.js +++ b/dom/indexedDB/test/unit/test_put_get_values.js @@ -11,16 +11,17 @@ function testSteps() const objectStoreName = "Objects"; let testString = { key: 0, value: "testString" }; let testInt = { key: 1, value: 1002 }; let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; let objectStore = db.createObjectStore(objectStoreName, { autoIncrement: 0 }); request = objectStore.add(testString.value, testString.key); @@ -37,15 +38,18 @@ function testSteps() request = objectStore.add(testInt.value, testInt.key); request.onerror = errorHandler; request.onsuccess = function(event) { is(event.target.result, testInt.key, "Got the right key"); request = objectStore.get(testInt.key); request.onerror = errorHandler; request.onsuccess = function(event) { is(event.target.result, testInt.value, "Got the right value"); - finishTest(); }; } + // Wait for success + yield undefined; + + finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_put_get_values_autoIncrement.js +++ b/dom/indexedDB/test/unit/test_put_get_values_autoIncrement.js @@ -11,16 +11,17 @@ function testSteps() const objectStoreName = "Objects"; let testString = { value: "testString" }; let testInt = { value: 1002 }; let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; let objectStore = db.createObjectStore(objectStoreName, { autoIncrement: 1 }); request = objectStore.put(testString.value); @@ -37,14 +38,17 @@ function testSteps() request = objectStore.put(testInt.value); request.onerror = errorHandler; request.onsuccess = function(event) { testInt.key = event.target.result; request = objectStore.get(testInt.key); request.onerror = errorHandler; request.onsuccess = function(event) { is(event.target.result, testInt.value, "Got the right value"); - finishTest(); }; } + // Wait for success yield undefined; -} \ No newline at end of file + + finishTest(); + yield undefined; +}
--- a/dom/indexedDB/test/unit/test_remove_objectStore.js +++ b/dom/indexedDB/test/unit/test_remove_objectStore.js @@ -8,18 +8,22 @@ var testGenerator = testSteps(); function testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; const objectStoreName = "Objects"; let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = unexpectedSuccessHandler; let event = yield undefined; + request.onupgradeneeded = unexpectedSuccessHandler; + request.onsuccess = grabEventAndContinueHandler; + let db = event.target.result; is(db.objectStoreNames.length, 0, "Correct objectStoreNames list"); let objectStore = db.createObjectStore(objectStoreName, { keyPath: "foo" }); let addedCount = 0; @@ -35,23 +39,30 @@ function testSteps() yield undefined; is(db.objectStoreNames.length, 1, "Correct objectStoreNames list"); is(db.objectStoreNames.item(0), objectStoreName, "Correct name"); event.target.transaction.oncomplete = grabEventAndContinueHandler; event = yield undefined; + // Wait for success. + event = yield undefined; + db.close(); request = indexedDB.open(name, 2); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = unexpectedSuccessHandler; event = yield undefined; + request.onupgradeneeded = unexpectedSuccessHandler; + request.onsuccess = grabEventAndContinueHandler; + db = event.target.result; let trans = event.target.transaction; let oldObjectStore = trans.objectStore(objectStoreName); isnot(oldObjectStore, null, "Correct object store prior to deleting"); db.deleteObjectStore(objectStoreName); is(db.objectStoreNames.length, 0, "Correct objectStores list"); try { @@ -82,16 +93,19 @@ function testSteps() is(db.objectStoreNames.length, 0, "Correct objectStores list"); continueToNextStep(); yield undefined; trans.oncomplete = grabEventAndContinueHandler; event = yield undefined; + // Wait for success. + event = yield undefined; + db.close(); request = indexedDB.open(name, 3); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; event = yield undefined; db = event.target.result;
--- a/dom/indexedDB/test/unit/test_request_readyState.js +++ b/dom/indexedDB/test/unit/test_request_readyState.js @@ -9,16 +9,17 @@ function testSteps() { const name = this.window ? window.location.pathname : "Splendid Test"; let request = indexedDB.open(name, 1); is(request.readyState, "pending", "Correct readyState"); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; is(request.readyState, "done", "Correct readyState"); let db = event.target.result; let objectStore = db.createObjectStore("foo"); let key = 10; @@ -37,11 +38,14 @@ function testSteps() request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; is(request.readyState, "pending", "Correct readyState"); event = yield undefined; ok(event.target.result, "Got something"); is(request.readyState, "done", "Correct readyState"); + // Wait for success + yield undefined; + finishTest(); yield undefined; }
--- a/dom/indexedDB/test/unit/test_setVersion.js +++ b/dom/indexedDB/test/unit/test_setVersion.js @@ -10,41 +10,42 @@ function testSteps() const name = this.window ? window.location.pathname : "Splendid Test"; let request = indexedDB.open(name, 1); request.onerror = errorHandler; request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; + db.close(); // Check default state. is(db.version, 1, "Correct default version for a new database."); const versions = [ 7, 42, ]; - db.close(); - for (let i = 0; i < versions.length; i++) { let version = versions[i]; let request = indexedDB.open(name, version); request.onerror = errorHandler; request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = grabEventAndContinueHandler; let event = yield undefined; let db = event.target.result; is(db.version, version, "Database version number updated correctly"); is(event.target.transaction.mode, "versionchange", "Correct mode"); - executeSoon(function() { testGenerator.next(); }); + // Wait for success yield undefined; + db.close(); } finishTest(); yield undefined; }
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -28,17 +28,17 @@ interface nsIServiceWorkerInfo : nsISupp readonly attribute DOMString scope; readonly attribute DOMString scriptSpec; readonly attribute DOMString currentWorkerURL; readonly attribute DOMString activeCacheName; readonly attribute DOMString waitingCacheName; }; -[scriptable, builtinclass, uuid(5e112a42-df4c-4ae9-bc71-e6e681ab5f38)] +[scriptable, builtinclass, uuid(aee94712-9adb-4c0b-80a7-a8df34dfa2e8)] interface nsIServiceWorkerManager : nsISupports { /** * Registers a ServiceWorker with script loaded from `aScriptURI` to act as * the ServiceWorker for aScope. Requires a valid entry settings object on * the stack. This means you must call this from content code 'within' * a window. * @@ -100,22 +100,17 @@ interface nsIServiceWorkerManager : nsIS * Clears ServiceWorker registrations from memory and disk for the specified * host. * - All ServiceWorker instances change their state to redundant. * - Existing ServiceWorker instances handling fetches will keep running. * - All documents will immediately stop being controlled. * - Unregister jobs will be queued for all registrations. * This eventually results in the registration being deleted from disk too. */ - void remove(in AUTF8String aHost); - - /* - * Clear all registrations for all hosts. See remove(). - */ - void removeAll(); + void removeAndPropagate(in AUTF8String aHost); // Testing DOMString getScopeForUrl(in nsIPrincipal aPrincipal, in DOMString aPath); // Note: This is meant to be used only by about:serviceworkers. //It returns an array of nsIServiceWorkerInfo. nsIArray getAllRegistrations();
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1239,36 +1239,16 @@ ContentChild::RecvUpdateServiceWorkerReg { nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager(); if (swm) { swm->UpdateAllRegistrations(); } return true; } -bool -ContentChild::RecvRemoveServiceWorkerRegistrationsForDomain(const nsString& aDomain) -{ - nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager(); - if (swm) { - swm->Remove(NS_ConvertUTF16toUTF8(aDomain)); - } - return true; -} - -bool -ContentChild::RecvRemoveServiceWorkerRegistrations() -{ - nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager(); - if (swm) { - swm->RemoveAll(); - } - return true; -} - static CancelableTask* sFirstIdleTask; static void FirstIdle(void) { MOZ_ASSERT(sFirstIdleTask); sFirstIdleTask = nullptr; ContentChild::GetSingleton()->SendFirstIdle(); }
--- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -300,20 +300,16 @@ public: virtual bool RecvSetConnectivity(const bool& connectivity) override; virtual bool RecvSpeakerManagerNotify() override; virtual bool RecvBidiKeyboardNotify(const bool& isLangRTL) override; virtual bool RecvUpdateServiceWorkerRegistrations() override; - virtual bool RecvRemoveServiceWorkerRegistrationsForDomain(const nsString& aDomain) override; - - virtual bool RecvRemoveServiceWorkerRegistrations() override; - virtual bool RecvNotifyVisited(const URIParams& aURI) override; // auto remove when alertfinished is received. nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver); virtual bool RecvSystemMemoryAvailable(const uint64_t& aGetterId, const uint32_t& aMemoryAvailable) override; virtual bool RecvPreferenceUpdate(const PrefSetting& aPref) override;
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -501,20 +501,16 @@ child: /** * Communication between the PuppetBidiKeyboard and the actual * BidiKeyboard hosted by the parent */ async BidiKeyboardNotify(bool isLangRTL); async UpdateServiceWorkerRegistrations(); - async RemoveServiceWorkerRegistrationsForDomain(nsString aDomain); - - async RemoveServiceWorkerRegistrations(); - async DataStoreNotify(uint32_t aAppId, nsString aName, nsString aManifestURL); /** * Dump this process's GC and CC logs to the provided files. * * For documentation on the other args, see dumpGCAndCCLogsToFile in * nsIMemoryInfoDumper.idl
--- a/dom/ipc/ProcessHangMonitor.cpp +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -151,17 +151,17 @@ public: NS_IMETHOD BeginStartingDebugger() override; NS_IMETHOD EndStartingDebugger() override; NS_IMETHOD TerminatePlugin() override; NS_IMETHOD TerminateProcess() override; NS_IMETHOD UserCanceled() override; NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) override; - // Called on xpcom shutdown + // Called when a content process shuts down. void Clear() { mContentParent = nullptr; mActor = nullptr; } void SetHangData(const HangData& aHangData) { mHangData = aHangData; } void SetBrowserDumpId(nsAutoString& aId) { mBrowserDumpId = aId; @@ -451,21 +451,35 @@ HangMonitorParent::HangMonitorParent(Pro mMonitor("HangMonitorParent lock"), mShutdownDone(false), mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock") { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false); } +static PLDHashOperator +DeleteMinidump(const uint32_t& aPluginId, nsString aCrashId, void* aUserData) +{ +#ifdef MOZ_CRASHREPORTER + if (!aCrashId.IsEmpty()) { + CrashReporter::DeleteMinidumpFilesForID(aCrashId); + } +#endif + return PL_DHASH_NEXT; +} + HangMonitorParent::~HangMonitorParent() { // For some reason IPDL doesn't autmatically delete the channel for a // bridged protocol (bug 1090570). So we have to do it ourselves. XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(GetTransport())); + + MutexAutoLock lock(mBrowserCrashDumpHashLock); + mBrowserCrashDumpIds.EnumerateRead(DeleteMinidump, nullptr); } void HangMonitorParent::Shutdown() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MonitorAutoLock lock(mMonitor); @@ -800,30 +814,32 @@ HangMonitoredProcess::TerminatePlugin() MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TPluginHangData) { return NS_ERROR_UNEXPECTED; } uint32_t id = mHangData.get_PluginHangData().pluginId(); plugins::TerminatePlugin(id, mBrowserDumpId); - mActor->CleanupPluginHang(id, false); + if (mActor) { + mActor->CleanupPluginHang(id, false); + } return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::TerminateProcess() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!mContentParent) { return NS_ERROR_UNEXPECTED; } - if (mHangData.type() == HangData::TPluginHangData) { + if (mActor && mHangData.type() == HangData::TPluginHangData) { uint32_t id = mHangData.get_PluginHangData().pluginId(); mActor->CleanupPluginHang(id, true); } mContentParent->KillHard("HangMonitor"); return NS_OK; } @@ -850,18 +866,20 @@ HangMonitoredProcess::IsReportForBrowser NS_IMETHODIMP HangMonitoredProcess::UserCanceled() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TPluginHangData) { return NS_OK; } - uint32_t id = mHangData.get_PluginHangData().pluginId(); - mActor->CleanupPluginHang(id, true); + if (mActor) { + uint32_t id = mHangData.get_PluginHangData().pluginId(); + mActor->CleanupPluginHang(id, true); + } return NS_OK; } ProcessHangMonitor* ProcessHangMonitor::sInstance; ProcessHangMonitor::ProcessHangMonitor() : mCPOWTimeout(false) {
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -2141,16 +2141,23 @@ TabChild::RecvHandleLongTap(const CSSPoi } bool TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId, const APZStateChange& aChange, const int& aArg) { mAPZEventState->ProcessAPZStateChange(GetDocument(), aViewId, aChange, aArg); + if (aChange == APZStateChange::TransformEnd) { + // This is used by tests to determine when the APZ is done doing whatever + // it's doing. XXX generify this as needed when writing additional tests. + DispatchMessageManagerMessage( + NS_LITERAL_STRING("APZ:TransformEnd"), + NS_LITERAL_STRING("{}")); + } return true; } bool TabChild::RecvNotifyFlushComplete() { APZCCallbackHelper::NotifyFlushComplete(); return true;
--- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -483,21 +483,23 @@ SourceBuffer::AppendDataCompletedWithSuc mActive = true; mMediaSource->SourceBufferIsActive(this); mMediaSource->QueueInitializationEvent(); if (mIsUsingFormatReader) { mMediaSource->GetDecoder()->NotifyWaitingForResourcesStatusChanged(); } } } - if (mActive) { + if (mActive && mIsUsingFormatReader) { // Tell our parent decoder that we have received new data. // The information provided do not matter much so long as it is monotonically // increasing. mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++); + // Send progress event. + mMediaSource->GetDecoder()->NotifyBytesDownloaded(); } CheckEndTime(); StopUpdating(); } void
--- a/dom/plugins/base/PluginPRLibrary.h +++ b/dom/plugins/base/PluginPRLibrary.h @@ -118,16 +118,17 @@ public: #endif virtual nsresult SetBackgroundUnknown(NPP instance) override; virtual nsresult BeginUpdateBackground(NPP instance, const nsIntRect&, gfxContext** aCtx) override; virtual nsresult EndUpdateBackground(NPP instance, gfxContext* aCtx, const nsIntRect&) override; virtual void GetLibraryPath(nsACString& aPath) { aPath.Assign(mFilePath); } virtual nsresult GetRunID(uint32_t* aRunID) override { return NS_ERROR_NOT_IMPLEMENTED; } + virtual void SetHasLocalInstance() override { } private: NP_InitializeFunc mNP_Initialize; NP_ShutdownFunc mNP_Shutdown; NP_GetMIMEDescriptionFunc mNP_GetMIMEDescription; #if defined(XP_UNIX) && !defined(XP_MACOSX) NP_GetValueFunc mNP_GetValue; #endif
--- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -950,16 +950,18 @@ nsPluginHost::TrySetUpPluginInstance(con if (!plugin) { return NS_ERROR_FAILURE; } nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); NS_ASSERTION(pluginTag, "Must have plugin tag here!"); + plugin->GetLibrary()->SetHasLocalInstance(); + #if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_CRASHREPORTER) if (pluginTag->mIsFlashPlugin) { CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FlashVersion"), pluginTag->mVersion); } #endif nsRefPtr<nsNPAPIPluginInstance> instance = new nsNPAPIPluginInstance();
--- a/dom/plugins/base/nsPluginTags.cpp +++ b/dom/plugins/base/nsPluginTags.cpp @@ -143,16 +143,17 @@ GetStatePrefNameForPlugin(nsPluginTag* a uint32_t nsPluginTag::sNextId; nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo, int64_t aLastModifiedTime, bool fromExtension) : mId(sNextId++), mContentProcessRunningCount(0), + mHadLocalInstance(false), mName(aPluginInfo->fName), mDescription(aPluginInfo->fDescription), mLibrary(nullptr), mIsJavaPlugin(false), mIsFlashPlugin(false), mFileName(aPluginInfo->fFileName), mFullPath(aPluginInfo->fFullPath), mVersion(aPluginInfo->fVersion),
--- a/dom/plugins/base/nsPluginTags.h +++ b/dom/plugins/base/nsPluginTags.h @@ -85,16 +85,20 @@ public: bool IsFromExtension() const; nsRefPtr<nsPluginTag> mNext; uint32_t mId; // Number of PluginModuleParents living in all content processes. size_t mContentProcessRunningCount; + + // True if we've ever created an instance of this plugin in the current process. + bool mHadLocalInstance; + nsCString mName; // UTF-8 nsCString mDescription; // UTF-8 nsTArray<nsCString> mMimeTypes; // UTF-8 nsTArray<nsCString> mMimeDescriptions; // UTF-8 nsTArray<nsCString> mExtensions; // UTF-8 PRLibrary *mLibrary; nsRefPtr<nsNPAPIPlugin> mPlugin; bool mIsJavaPlugin;
--- a/dom/plugins/ipc/PluginHangUIParent.cpp +++ b/dom/plugins/ipc/PluginHangUIParent.cpp @@ -348,18 +348,17 @@ PluginHangUIParent::RecvUserResponse(con } mLastUserResponse = aResponse; mResponseTicks = ::GetTickCount(); mIsShowing = false; // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel int responseCode; if (aResponse & HANGUI_USER_RESPONSE_STOP) { // User clicked Stop - nsString dummy; - mModule->TerminateChildProcess(mMainThreadMessageLoop, &dummy); + mModule->TerminateChildProcess(mMainThreadMessageLoop, EmptyString()); responseCode = 1; } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) { mModule->OnHangUIContinue(); // User clicked Continue responseCode = 2; } else { // Dialog was cancelled responseCode = 3;
--- a/dom/plugins/ipc/PluginLibrary.h +++ b/dom/plugins/ipc/PluginLibrary.h @@ -80,14 +80,15 @@ public: * API. */ virtual nsresult SetBackgroundUnknown(NPP instance) = 0; virtual nsresult BeginUpdateBackground(NPP instance, const nsIntRect&, gfxContext**) = 0; virtual nsresult EndUpdateBackground(NPP instance, gfxContext*, const nsIntRect&) = 0; virtual nsresult GetRunID(uint32_t* aRunID) = 0; + virtual void SetHasLocalInstance() = 0; }; } // namespace mozilla #endif // ifndef mozilla_PluginLibrary_h
--- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -62,17 +62,16 @@ using mozilla::dom::PCrashReporterChild; #if defined(XP_WIN) const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen"; const wchar_t * kMozillaWindowClass = L"MozillaWindowClass"; #endif namespace { // see PluginModuleChild::GetChrome() PluginModuleChild* gChromeInstance = nullptr; -nsTArray<PluginModuleChild*>* gAllInstances; } #ifdef MOZ_WIDGET_QT typedef void (*_gtk_init_fn)(int argc, char **argv); static _gtk_init_fn s_gtk_init = nullptr; static PRLibrary *sGtkLib = nullptr; #endif @@ -122,42 +121,38 @@ PluginModuleChild::CreateForContentProce return child; } PluginModuleChild::PluginModuleChild(bool aIsChrome) : mLibrary(0) , mPluginFilename("") , mQuirks(QUIRKS_NOT_INITIALIZED) , mIsChrome(aIsChrome) + , mHasShutdown(false) , mTransport(nullptr) , mShutdownFunc(0) , mInitializeFunc(0) #if defined(OS_WIN) || defined(OS_MACOSX) , mGetEntryPointsFunc(0) #elif defined(MOZ_WIDGET_GTK) , mNestedLoopTimerId(0) #elif defined(MOZ_WIDGET_QT) , mNestedLoopTimerObject(0) #endif #ifdef OS_WIN , mNestedEventHook(nullptr) , mGlobalCallWndProcHook(nullptr) #endif { - if (!gAllInstances) { - gAllInstances = new nsTArray<PluginModuleChild*>(1); - } - gAllInstances->AppendElement(this); - memset(&mFunctions, 0, sizeof(mFunctions)); if (mIsChrome) { MOZ_ASSERT(!gChromeInstance); gChromeInstance = this; } - mUserAgent.SetIsVoid(true); + #ifdef XP_MACOSX if (aIsChrome) { mac_plugin_interposing::child::SetUpCocoaInterposing(); } #endif } PluginModuleChild::~PluginModuleChild() @@ -165,23 +160,16 @@ PluginModuleChild::~PluginModuleChild() if (mTransport) { // For some reason IPDL doesn't autmatically delete the channel for a // bridged protocol (bug 1090570). So we have to do it ourselves. This // code is only invoked for PluginModuleChild instances created via // bridging; otherwise mTransport is null. XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(mTransport)); } - gAllInstances->RemoveElement(this); - MOZ_ASSERT_IF(mIsChrome, gAllInstances->Length() == 0); - if (gAllInstances->IsEmpty()) { - delete gAllInstances; - gAllInstances = nullptr; - } - if (mIsChrome) { MOZ_ASSERT(gChromeInstance == this); // We don't unload the plugin library in case it uses atexit handlers or // other similar hooks. DeinitGraphics(); PluginScriptableObjectChild::ClearIdentifiers(); @@ -689,40 +677,53 @@ PluginModuleChild::DeinitGraphics() #if defined(MOZ_X11) && defined(NS_FREE_PERMANENT_DATA) // We free some data off of XDisplay close hooks, ensure they're // run. Closing the display is pretty scary, so we only do it to // silence leak checkers. XCloseDisplay(DefaultXDisplay()); #endif } -bool -PluginModuleChild::AnswerNP_Shutdown(NPError *rv) +NPError +PluginModuleChild::NP_Shutdown() { AssertPluginThread(); MOZ_ASSERT(mIsChrome); + if (mHasShutdown) { + return NPERR_NO_ERROR; + } + #if defined XP_WIN mozilla::widget::StopAudioSession(); #endif // the PluginModuleParent shuts down this process after this interrupt // call pops off its stack - *rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR; + NPError rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR; // weakly guard against re-entry after NP_Shutdown memset(&mFunctions, 0, sizeof(mFunctions)); #ifdef OS_WIN ResetEventHooks(); #endif GetIPCChannel()->SetAbortOnError(false); + mHasShutdown = true; + + return rv; +} + +bool +PluginModuleChild::AnswerNP_Shutdown(NPError *rv) +{ + *rv = NP_Shutdown(); return true; } bool PluginModuleChild::AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify, bool *aClearSiteData, bool *aGetSitesWithData) { @@ -834,16 +835,21 @@ PluginModuleChild::ActorDestroy(ActorDes return; } if (AbnormalShutdown == why) { NS_WARNING("shutting down early because of crash!"); QuickExit(); } + if (!mHasShutdown) { + MOZ_ASSERT(gChromeInstance == this); + NP_Shutdown(); + } + // doesn't matter why we're being destroyed; it's up to us to // initiate (clean) shutdown XRE_ShutdownChildProcess(); } void PluginModuleChild::CleanUp() {
--- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -172,16 +172,18 @@ public: IPC::Channel* aChannel); static PluginModuleChild* CreateForContentProcess(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess); void CleanUp(); + NPError NP_Shutdown(); + const char* GetUserAgent(); static const NPNetscapeFuncs sBrowserFuncs; static PluginModuleChild* GetChrome(); /** * The child implementation of NPN_CreateObject. @@ -318,20 +320,20 @@ private: #elif defined(MOZ_WIDGET_QT) virtual void EnteredCxxStack() override; virtual void ExitedCxxStack() override; #endif PRLibrary* mLibrary; nsCString mPluginFilename; // UTF8 - nsCString mUserAgent; int mQuirks; bool mIsChrome; + bool mHasShutdown; // true if NP_Shutdown has run Transport* mTransport; // we get this from the plugin NP_PLUGINSHUTDOWN mShutdownFunc; #if defined(OS_LINUX) || defined(OS_BSD) NP_PLUGINUNIXINIT mInitializeFunc; #elif defined(OS_WIN) || defined(OS_MACOSX) NP_PLUGININIT mInitializeFunc;
--- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -354,20 +354,19 @@ mozilla::plugins::TerminatePlugin(uint32 { MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst(); nsPluginTag* pluginTag = host->PluginWithId(aPluginId); if (!pluginTag || !pluginTag->mPlugin) { return; } - nsAutoString dumpId(aBrowserDumpId); nsRefPtr<nsNPAPIPlugin> plugin = pluginTag->mPlugin; PluginModuleChromeParent* chromeParent = static_cast<PluginModuleChromeParent*>(plugin->GetLibrary()); - chromeParent->TerminateChildProcess(MessageLoop::current(), &dumpId); + chromeParent->TerminateChildProcess(MessageLoop::current(), aBrowserDumpId); } /* static */ PluginLibrary* PluginModuleContentParent::LoadModule(uint32_t aPluginId) { PluginModuleMapping::NotifyLoadingModule loadingModule; nsAutoPtr<PluginModuleMapping> mapping(new PluginModuleMapping(aPluginId)); @@ -617,16 +616,17 @@ PluginModuleChromeParent::WaitForIPCConn return false; } return true; } PluginModuleParent::PluginModuleParent(bool aIsChrome) : mIsChrome(aIsChrome) , mShutdown(false) + , mHadLocalInstance(false) , mClearSiteDataSupported(false) , mGetSitesWithDataSupported(false) , mNPNIface(nullptr) , mNPPIface(nullptr) , mPlugin(nullptr) , mTaskFactory(this) , mIsFlashPlugin(false) , mIsStartingAsync(false) @@ -1149,18 +1149,17 @@ PluginModuleChromeParent::ShouldContinue #ifdef XP_WIN if (LaunchHangUI()) { return true; } // If LaunchHangUI returned false then we should proceed with the // original plugin hang behaviour and kill the plugin container. FinishHangUI(); #endif // XP_WIN - nsString dummy; - TerminateChildProcess(MessageLoop::current(), &dummy); + TerminateChildProcess(MessageLoop::current(), EmptyString()); GetIPCChannel()->CloseWithTimeout(); return false; } bool PluginModuleContentParent::ShouldContinueFromReplyTimeout() { nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); @@ -1174,17 +1173,17 @@ PluginModuleContentParent::ShouldContinu void PluginModuleContentParent::OnExitedSyncSend() { ProcessHangMonitor::ClearHang(); } void PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop, - nsAString* aBrowserDumpId) + const nsAString& aBrowserDumpId) { #ifdef MOZ_CRASHREPORTER #ifdef XP_WIN mozilla::MutexAutoLock lock(mCrashReporterMutex); CrashReporterParent* crashReporter = mCrashReporter; if (!crashReporter) { // If mCrashReporter is null then the hang has ended, the plugin module // is shutting down. There's nothing to do here. @@ -1210,28 +1209,28 @@ PluginModuleChromeParent::TerminateChild bool reportsReady = false; // Check to see if we already have a browser dump id - with e10s plugin // hangs we take this earlier (see ProcessHangMonitor) from a background // thread. We do this before we message the main thread about the hang // since the posted message will trash our browser stack state. bool exists; nsCOMPtr<nsIFile> browserDumpFile; - if (aBrowserDumpId && !aBrowserDumpId->IsEmpty() && - CrashReporter::GetMinidumpForID(*aBrowserDumpId, getter_AddRefs(browserDumpFile)) && + if (!aBrowserDumpId.IsEmpty() && + CrashReporter::GetMinidumpForID(aBrowserDumpId, getter_AddRefs(browserDumpFile)) && browserDumpFile && NS_SUCCEEDED(browserDumpFile->Exists(&exists)) && exists) { // We have a single browser report, generate a new plugin process parent // report and pair it up with the browser report handed in. reportsReady = crashReporter->GenerateMinidumpAndPair(this, browserDumpFile, NS_LITERAL_CSTRING("browser")); if (!reportsReady) { browserDumpFile = nullptr; - CrashReporter::DeleteMinidumpFilesForID(*aBrowserDumpId); + CrashReporter::DeleteMinidumpFilesForID(aBrowserDumpId); } } // Generate crash report including plugin and browser process minidumps. // The plugin process is the parent report with additional dumps including // the browser process, content process when running under e10s, and // various flash subprocesses if we're the flash module. if (!reportsReady) { @@ -2382,17 +2381,22 @@ PluginModuleParent::NP_Shutdown(NPError* return NS_OK; } bool PluginModuleParent::DoShutdown(NPError* error) { bool ok = true; - if (IsChrome()) { + if (IsChrome() && mHadLocalInstance) { + // We synchronously call NP_Shutdown if the chrome process was using + // plugins itself. That way we can service any requests the plugin + // makes. If we're in e10s, though, the content processes will have + // already shut down and there's no one to talk to. So we shut down + // asynchronously in PluginModuleChild::ActorDestroy. ok = CallNP_Shutdown(error); } // if NP_Shutdown() is nested within another interrupt call, this will // break things. but lord help us if we're doing that anyway; the // plugin dso will have been unloaded on the other side by the // CallNP_Shutdown() message Close();
--- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -128,16 +128,19 @@ public: virtual bool WaitForIPCConnection() { return true; } nsCString GetHistogramKey() const { return mPluginName + mPluginVersion; } virtual nsresult GetRunID(uint32_t* aRunID) override; + virtual void SetHasLocalInstance() override { + mHadLocalInstance = true; + } protected: virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace(const Message& parent, const Message& child) override { return MediateRace(parent, child); } @@ -279,16 +282,17 @@ protected: bool MaybeRunDeferredShutdown(); bool DoShutdown(NPError* error); bool GetSetting(NPNVariable aVariable); void GetSettings(PluginSettings* aSettings); bool mIsChrome; bool mShutdown; + bool mHadLocalInstance; bool mClearSiteDataSupported; bool mGetSitesWithDataSupported; NPNetscapeFuncs* mNPNIface; NPPluginFuncs* mNPPIface; nsNPAPIPlugin* mPlugin; TaskFactory<PluginModuleParent> mTaskFactory; nsString mPluginDumpID; nsString mBrowserDumpID; @@ -382,17 +386,17 @@ class PluginModuleChromeParent * * @param aMsgLoop the main message pump associated with the module * protocol. * @param aBrowserDumpId (optional) previously taken browser dump id. If * provided TerminateChildProcess will use this browser dump file in * generating a multi-process crash report. If not provided a browser * dump will be taken at the time of this call. */ - void TerminateChildProcess(MessageLoop* aMsgLoop, nsAString* aBrowserDumpId); + void TerminateChildProcess(MessageLoop* aMsgLoop, const nsAString& aBrowserDumpId); #ifdef XP_WIN /** * Called by Plugin Hang UI to notify that the user has clicked continue. * Used for chrome hang annotations. */ void OnHangUIContinue();
--- a/dom/webidl/MouseEvent.webidl +++ b/dom/webidl/MouseEvent.webidl @@ -11,17 +11,19 @@ */ [Constructor(DOMString typeArg, optional MouseEventInit mouseEventInitDict)] interface MouseEvent : UIEvent { readonly attribute long screenX; readonly attribute long screenY; readonly attribute long clientX; readonly attribute long clientY; +[Pref="dom.mouseEvent.offsetXY.enabled"] readonly attribute long offsetX; +[Pref="dom.mouseEvent.offsetXY.enabled"] readonly attribute long offsetY; readonly attribute boolean ctrlKey; readonly attribute boolean shiftKey; readonly attribute boolean altKey; readonly attribute boolean metaKey; readonly attribute short button; readonly attribute unsigned short buttons; readonly attribute EventTarget? relatedTarget;
--- a/dom/webidl/ServiceWorkerGlobalScope.webidl +++ b/dom/webidl/ServiceWorkerGlobalScope.webidl @@ -16,16 +16,18 @@ interface ServiceWorkerGlobalScope : Wor readonly attribute Clients clients; readonly attribute ServiceWorkerRegistration registration; [Throws] Promise<boolean> skipWaiting(); attribute EventHandler oninstall; attribute EventHandler onactivate; + + [Func="mozilla::dom::workers::ServiceWorkerGlobalScope::InterceptionEnabled"] attribute EventHandler onfetch; attribute EventHandler onbeforeevicted; attribute EventHandler onevicted; // The event.source of these MessageEvents are instances of Client attribute EventHandler onmessage; };
--- a/dom/workers/PServiceWorkerManager.ipdl +++ b/dom/workers/PServiceWorkerManager.ipdl @@ -20,20 +20,26 @@ parent: Register(ServiceWorkerRegistrationData data); Unregister(PrincipalInfo principalInfo, nsString scope); PropagateSoftUpdate(OriginAttributes originAttributes, nsString scope); PropagateUnregister(PrincipalInfo principalInfo, nsString scope); + PropagateRemove(nsCString host); + + PropagateRemoveAll(); + Shutdown(); child: NotifyRegister(ServiceWorkerRegistrationData data); NotifySoftUpdate(OriginAttributes originAttributes, nsString scope); NotifyUnregister(PrincipalInfo principalInfo, nsString scope); + NotifyRemove(nsCString host); + NotifyRemoveAll(); __delete__(); }; } // namespace dom } // namespace mozilla
--- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -156,16 +156,17 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= #define DUMP_CONTROLLED_BY_PREF 1 #define PREF_DOM_WINDOW_DUMP_ENABLED "browser.dom.window.dump.enabled" #endif #define PREF_DOM_CACHES_ENABLED "dom.caches.enabled" #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion" #define PREF_INTL_ACCEPT_LANGUAGES "intl.accept_languages" #define PREF_SERVICEWORKERS_ENABLED "dom.serviceWorkers.enabled" +#define PREF_INTERCEPTION_ENABLED "dom.serviceWorkers.interception.enabled" namespace { const uint32_t kNoIndex = uint32_t(-1); const JS::ContextOptions kRequiredContextOptions = JS::ContextOptions().setDontReportUncaught(true); @@ -1900,16 +1901,20 @@ RuntimeService::Init() NS_FAILED(Preferences::RegisterCallbackAndCall( WorkerPrefChanged, PREF_DOM_CACHES_ENABLED, reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) || NS_FAILED(Preferences::RegisterCallbackAndCall( WorkerPrefChanged, PREF_SERVICEWORKERS_ENABLED, reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) || + NS_FAILED(Preferences::RegisterCallbackAndCall( + WorkerPrefChanged, + PREF_INTERCEPTION_ENABLED, + reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) || NS_FAILED(Preferences::RegisterCallback(LoadRuntimeOptions, PREF_JS_OPTIONS_PREFIX, nullptr)) || NS_FAILED(Preferences::RegisterCallbackAndCall( LoadRuntimeOptions, PREF_WORKERS_OPTIONS_PREFIX, nullptr)) || NS_FAILED(Preferences::RegisterCallbackAndCall(PrefLanguagesChanged, @@ -2097,16 +2102,20 @@ RuntimeService::Cleanup() NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeOptions, PREF_JS_OPTIONS_PREFIX, nullptr)) || NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeOptions, PREF_WORKERS_OPTIONS_PREFIX, nullptr)) || NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, + PREF_INTERCEPTION_ENABLED, + reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) || + NS_FAILED(Preferences::UnregisterCallback( + WorkerPrefChanged, PREF_SERVICEWORKERS_ENABLED, reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) || NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, PREF_DOM_CACHES_ENABLED, reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) || #if DUMP_CONTROLLED_BY_PREF NS_FAILED(Preferences::UnregisterCallback( @@ -2651,16 +2660,20 @@ RuntimeService::WorkerPrefChanged(const if (key == WORKERPREF_DOM_CACHES) { key = WORKERPREF_DOM_CACHES; sDefaultPreferences[WORKERPREF_DOM_CACHES] = Preferences::GetBool(PREF_DOM_CACHES_ENABLED, false); } else if (key == WORKERPREF_SERVICEWORKERS) { key = WORKERPREF_SERVICEWORKERS; sDefaultPreferences[WORKERPREF_SERVICEWORKERS] = Preferences::GetBool(PREF_SERVICEWORKERS_ENABLED, false); + } else if (key == WORKERPREF_INTERCEPTION_ENABLED) { + key = WORKERPREF_INTERCEPTION_ENABLED; + sDefaultPreferences[key] = + Preferences::GetBool(PREF_INTERCEPTION_ENABLED, false); } // This function should never be registered as a callback for a preference it // does not handle. MOZ_ASSERT(key != WORKERPREF_COUNT); RuntimeService* rts = RuntimeService::GetService(); if (rts) { rts->UpdateAllWorkerPreference(key, sDefaultPreferences[key]);
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -735,17 +735,17 @@ public: NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); - swm->PropagateSoftUpdate(mOriginAttributes,mScope); + swm->PropagateSoftUpdate(mOriginAttributes, mScope); return NS_OK; } private: ~PropagateSoftUpdateRunnable() {} const OriginAttributes mOriginAttributes; @@ -784,16 +784,86 @@ private: ~PropagateUnregisterRunnable() {} nsCOMPtr<nsIPrincipal> mPrincipal; nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback; const nsString mScope; }; +class RemoveRunnable final : public nsRunnable +{ +public: + explicit RemoveRunnable(const nsACString& aHost) + {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->Remove(mHost); + return NS_OK; + } + +private: + ~RemoveRunnable() + {} + + const nsCString mHost; +}; + +class PropagateRemoveRunnable final : public nsRunnable +{ +public: + explicit PropagateRemoveRunnable(const nsACString& aHost) + {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->PropagateRemove(mHost); + return NS_OK; + } + +private: + ~PropagateRemoveRunnable() + {} + + const nsCString mHost; +}; + +class PropagateRemoveAllRunnable final : public nsRunnable +{ +public: + PropagateRemoveAllRunnable() + {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->PropagateRemoveAll(); + return NS_OK; + } + +private: + ~PropagateRemoveAllRunnable() + {} +}; + } // anonymous namespace class ServiceWorkerRegisterJob final : public ServiceWorkerJob, public serviceWorkerScriptCache::CompareCallback { friend class ContinueInstallTask; nsCString mScope; @@ -2308,16 +2378,19 @@ private: PrincipalInfo principalInfo; if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(mPrincipal, &principalInfo)))) { return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK; } nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm->mActor); + swm->mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(mScope)); + nsAutoCString scopeKey; nsresult rv = swm->PrincipalToScopeKey(mPrincipal, scopeKey); if (NS_WARN_IF(NS_FAILED(rv))) { return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK; } // "Let registration be the result of running [[Get Registration]] // algorithm passing scope as the argument." @@ -2350,19 +2423,16 @@ private: return NS_OK; } // "Invoke [[Clear Registration]]..." registration->Clear(); swm->RemoveRegistration(registration); } - MOZ_ASSERT(swm->mActor); - swm->mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(mScope)); - return NS_OK; } // The unregister job is done irrespective of success or failure of any sort. void UnregisterAndDone() { nsresult rv = Unregister(); @@ -4317,30 +4387,74 @@ ServiceWorkerManager::ForceUnregister(Re queue->CancelJobs(); } // Since Unregister is async, it is ok to call it in an enumeration. Unregister(aRegistration->mPrincipal, nullptr, NS_ConvertUTF8toUTF16(aRegistration->mScope)); } NS_IMETHODIMP +ServiceWorkerManager::RemoveAndPropagate(const nsACString& aHost) +{ + Remove(aHost); + PropagateRemove(aHost); + return NS_OK; +} + +void ServiceWorkerManager::Remove(const nsACString& aHost) { AssertIsOnMainThread(); + + // We need to postpone this operation in case we don't have an actor because + // this is needed by the ForceUnregister. + if (!mActor) { + nsRefPtr<nsIRunnable> runnable = new RemoveRunnable(aHost); + AppendPendingOperation(runnable); + return; + } + mRegistrationInfos.EnumerateRead(UnregisterIfMatchesHostPerPrincipal, &const_cast<nsACString&>(aHost)); - return NS_OK; } -NS_IMETHODIMP +void +ServiceWorkerManager::PropagateRemove(const nsACString& aHost) +{ + AssertIsOnMainThread(); + + if (!mActor) { + nsRefPtr<nsIRunnable> runnable = new PropagateRemoveRunnable(aHost); + AppendPendingOperation(runnable); + return; + } + + mActor->SendPropagateRemove(nsCString(aHost)); +} + +void ServiceWorkerManager::RemoveAll() { AssertIsOnMainThread(); mRegistrationInfos.EnumerateRead(UnregisterIfMatchesHostPerPrincipal, nullptr); - return NS_OK; +} + +void +ServiceWorkerManager::PropagateRemoveAll() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + if (!mActor) { + nsRefPtr<nsIRunnable> runnable = new PropagateRemoveAllRunnable(); + AppendPendingOperation(runnable); + return; + } + + mActor->SendPropagateRemoveAll(); } static PLDHashOperator UpdateEachRegistration(const nsACString& aKey, ServiceWorkerRegistrationInfo* aInfo, void* aUserArg) { auto This = static_cast<ServiceWorkerManager*>(aUserArg); MOZ_ASSERT(!aInfo->mScope.IsEmpty()); @@ -4380,33 +4494,29 @@ ServiceWorkerManager::UpdateAllRegistrat NS_IMETHODIMP ServiceWorkerManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); - nsAutoTArray<ContentParent*,1> children; - ContentParent::GetAll(children); - if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) { - for (uint32_t i = 0; i < children.Length(); i++) { - unused << children[i]->SendRemoveServiceWorkerRegistrations(); - } - RemoveAll(); - } else if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) { + PropagateRemoveAll(); + return NS_OK; + } + + if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) { nsAutoString domain(aData); - for (uint32_t i = 0; i < children.Length(); i++) { - unused << children[i]->SendRemoveServiceWorkerRegistrationsForDomain(domain); - } - - Remove(NS_ConvertUTF16toUTF8(domain)); - } else if (strcmp(aTopic, WEBAPPS_CLEAR_DATA) == 0) { + RemoveAndPropagate(NS_ConvertUTF16toUTF8(domain)); + return NS_OK; + } + + if (strcmp(aTopic, WEBAPPS_CLEAR_DATA) == 0) { nsCOMPtr<mozIApplicationClearPrivateDataParams> params = do_QueryInterface(aSubject); if (NS_WARN_IF(!params)) { return NS_OK; } uint32_t appId; nsresult rv = params->GetAppId(&appId);
--- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -303,16 +303,28 @@ public: void SoftUpdate(const OriginAttributes& aOriginAttributes, const nsACString& aScope); void PropagateSoftUpdate(const OriginAttributes& aOriginAttributes, const nsAString& aScope); + void + PropagateRemove(const nsACString& aHost); + + void + Remove(const nsACString& aHost); + + void + PropagateRemoveAll(); + + void + RemoveAll(); + already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration(nsIPrincipal* aPrincipal, const nsACString& aScope) const; ServiceWorkerRegistrationInfo* CreateNewRegistration(const nsCString& aScope, nsIPrincipal* aPrincipal); void RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
--- a/dom/workers/ServiceWorkerManagerChild.cpp +++ b/dom/workers/ServiceWorkerManagerChild.cpp @@ -62,11 +62,39 @@ ServiceWorkerManagerChild::RecvNotifyUnr return true; } nsresult rv = swm->Unregister(principal, nullptr, aScope); unused << NS_WARN_IF(NS_FAILED(rv)); return true; } +bool +ServiceWorkerManagerChild::RecvNotifyRemove(const nsCString& aHost) +{ + if (mShuttingDown) { + return true; + } + + nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->Remove(aHost); + return true; +} + +bool +ServiceWorkerManagerChild::RecvNotifyRemoveAll() +{ + if (mShuttingDown) { + return true; + } + + nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->RemoveAll(); + return true; +} + } // workers namespace } // dom namespace } // mozilla namespace
--- a/dom/workers/ServiceWorkerManagerChild.h +++ b/dom/workers/ServiceWorkerManagerChild.h @@ -37,16 +37,20 @@ public: override; virtual bool RecvNotifySoftUpdate(const OriginAttributes& aOriginAttributes, const nsString& aScope) override; virtual bool RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo, const nsString& aScope) override; + virtual bool RecvNotifyRemove(const nsCString& aHost) override; + + virtual bool RecvNotifyRemoveAll() override; + private: ServiceWorkerManagerChild() : mShuttingDown(false) {} ~ServiceWorkerManagerChild() {} bool mShuttingDown;
--- a/dom/workers/ServiceWorkerManagerParent.cpp +++ b/dom/workers/ServiceWorkerManagerParent.cpp @@ -252,16 +252,42 @@ ServiceWorkerManagerParent::RecvPropagat return false; } mService->PropagateUnregister(mID, aPrincipalInfo, aScope); return true; } bool +ServiceWorkerManagerParent::RecvPropagateRemove(const nsCString& aHost) +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(!mService)) { + return false; + } + + mService->PropagateRemove(mID, aHost); + return true; +} + +bool +ServiceWorkerManagerParent::RecvPropagateRemoveAll() +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(!mService)) { + return false; + } + + mService->PropagateRemoveAll(mID); + return true; +} + +bool ServiceWorkerManagerParent::RecvShutdown() { AssertIsOnBackgroundThread(); if (NS_WARN_IF(!mService)) { return false; }
--- a/dom/workers/ServiceWorkerManagerParent.h +++ b/dom/workers/ServiceWorkerManagerParent.h @@ -43,16 +43,20 @@ private: const nsString& aScope) override; virtual bool RecvPropagateSoftUpdate(const OriginAttributes& aOriginAttributes, const nsString& aScope) override; virtual bool RecvPropagateUnregister(const PrincipalInfo& aPrincipalInfo, const nsString& aScope) override; + virtual bool RecvPropagateRemove(const nsCString& aHost) override; + + virtual bool RecvPropagateRemoveAll() override; + virtual bool RecvShutdown() override; virtual void ActorDestroy(ActorDestroyReason aWhy) override; nsRefPtr<ServiceWorkerManagerService> mService; // We use this ID in the Service in order to avoid the sending of messages to // ourself.
--- a/dom/workers/ServiceWorkerManagerService.cpp +++ b/dom/workers/ServiceWorkerManagerService.cpp @@ -222,16 +222,103 @@ UnregisterEnumerator(nsPtrHashKey<Servic } else { data->mParentFound = true; #endif } return PL_DHASH_NEXT; } +struct MOZ_STACK_CLASS RemoveAllData final +{ + explicit RemoveAllData(uint64_t aParentID) + : mParentID(aParentID) +#ifdef DEBUG + , mParentFound(false) +#endif + { + MOZ_COUNT_CTOR(RemoveAllData); + } + + ~RemoveAllData() + { + MOZ_COUNT_DTOR(RemoveAllData); + } + + const uint64_t mParentID; +#ifdef DEBUG + bool mParentFound; +#endif +}; + +PLDHashOperator +RemoveAllEnumerator(nsPtrHashKey<ServiceWorkerManagerParent>* aKey, void* aPtr) +{ + AssertIsOnBackgroundThread(); + + auto* data = static_cast<RemoveAllData*>(aPtr); + ServiceWorkerManagerParent* parent = aKey->GetKey(); + MOZ_ASSERT(parent); + + if (parent->ID() != data->mParentID) { + unused << parent->SendNotifyRemoveAll(); +#ifdef DEBUG + } else { + data->mParentFound = true; +#endif + } + + return PL_DHASH_NEXT; +} + +struct MOZ_STACK_CLASS RemoveData final +{ + RemoveData(const nsACString& aHost, + uint64_t aParentID) + : mHost(aHost) + , mParentID(aParentID) +#ifdef DEBUG + , mParentFound(false) +#endif + { + MOZ_COUNT_CTOR(RemoveData); + } + + ~RemoveData() + { + MOZ_COUNT_DTOR(RemoveData); + } + + const nsCString mHost; + const uint64_t mParentID; +#ifdef DEBUG + bool mParentFound; +#endif +}; + +PLDHashOperator +RemoveEnumerator(nsPtrHashKey<ServiceWorkerManagerParent>* aKey, void* aPtr) +{ + AssertIsOnBackgroundThread(); + + auto* data = static_cast<RemoveData*>(aPtr); + ServiceWorkerManagerParent* parent = aKey->GetKey(); + MOZ_ASSERT(parent); + + if (parent->ID() != data->mParentID) { + unused << parent->SendNotifyRemove(data->mHost); +#ifdef DEBUG + } else { + data->mParentFound = true; +#endif + } + + return PL_DHASH_NEXT; +} + } // anonymous namespce void ServiceWorkerManagerService::PropagateRegistration( uint64_t aParentID, ServiceWorkerRegistrationData& aData) { AssertIsOnBackgroundThread(); @@ -280,11 +367,44 @@ ServiceWorkerManagerService::PropagateUn UnregisterData data(aPrincipalInfo, aScope, aParentID); mAgents.EnumerateEntries(UnregisterEnumerator, &data); #ifdef DEBUG MOZ_ASSERT(data.mParentFound); #endif } +void +ServiceWorkerManagerService::PropagateRemove(uint64_t aParentID, + const nsACString& aHost) +{ + AssertIsOnBackgroundThread(); + + RemoveData data(aHost, aParentID); + mAgents.EnumerateEntries(RemoveEnumerator, &data); + +#ifdef DEBUG + MOZ_ASSERT(data.mParentFound); +#endif +} + +void +ServiceWorkerManagerService::PropagateRemoveAll(uint64_t aParentID) +{ + AssertIsOnBackgroundThread(); + + nsRefPtr<dom::ServiceWorkerRegistrar> service = + dom::ServiceWorkerRegistrar::Get(); + MOZ_ASSERT(service); + + service->RemoveAll(); + + RemoveAllData data(aParentID); + mAgents.EnumerateEntries(RemoveAllEnumerator, &data); + +#ifdef DEBUG + MOZ_ASSERT(data.mParentFound); +#endif +} + } // workers namespace } // dom namespace } // mozilla namespace
--- a/dom/workers/ServiceWorkerManagerService.h +++ b/dom/workers/ServiceWorkerManagerService.h @@ -44,16 +44,20 @@ public: void PropagateSoftUpdate(uint64_t aParentID, const OriginAttributes& aOriginAttributes, const nsAString& aScope); void PropagateUnregister(uint64_t aParentID, const mozilla::ipc::PrincipalInfo& aPrincipalInfo, const nsAString& aScope); + void PropagateRemove(uint64_t aParentID, const nsACString& aHost); + + void PropagateRemoveAll(uint64_t aParentID); + private: ServiceWorkerManagerService(); ~ServiceWorkerManagerService(); nsTHashtable<nsPtrHashKey<ServiceWorkerManagerParent>> mAgents; }; } // workers namespace
--- a/dom/workers/ServiceWorkerRegistrar.cpp +++ b/dom/workers/ServiceWorkerRegistrar.cpp @@ -209,16 +209,41 @@ ServiceWorkerRegistrar::UnregisterServic } if (deleted) { ScheduleSaveData(); } } void +ServiceWorkerRegistrar::RemoveAll() +{ + AssertIsOnBackgroundThread(); + + if (mShuttingDown) { + NS_WARNING("Failed to remove all the serviceWorkers during shutting down."); + return; + } + + bool deleted = false; + + { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mDataLoaded); + + deleted = !mData.IsEmpty(); + mData.Clear(); + } + + if (deleted) { + ScheduleSaveData(); + } +} + +void ServiceWorkerRegistrar::LoadData() { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!mDataLoaded); nsresult rv = ReadData(); if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/dom/workers/ServiceWorkerRegistrar.h +++ b/dom/workers/ServiceWorkerRegistrar.h @@ -49,16 +49,17 @@ public: static already_AddRefed<ServiceWorkerRegistrar> Get(); void GetRegistrations(nsTArray<ServiceWorkerRegistrationData>& aValues); void RegisterServiceWorker(const ServiceWorkerRegistrationData& aData); void UnregisterServiceWorker(const mozilla::ipc::PrincipalInfo& aPrincipalInfo, const nsACString& aScope); + void RemoveAll(); protected: // These methods are protected because we test this class using gTest // subclassing it. void LoadData(); void SaveData(); nsresult ReadData();
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -4090,17 +4090,17 @@ WorkerPrivateParent<Derived>::SetBaseURI if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) { mLocationInfo.mSearch.Assign('?'); mLocationInfo.mSearch.Append(temp); } if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) { nsCOMPtr<nsITextToSubURI> converter = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID); - if (converter && nsContentUtils::EncodeDecodeURLHash()) { + if (converter && nsContentUtils::GettersDecodeURLHash()) { nsCString charset; nsAutoString unicodeRef; if (NS_SUCCEEDED(aBaseURI->GetOriginCharset(charset)) && NS_SUCCEEDED(converter->UnEscapeURIForUI(charset, temp, unicodeRef))) { mLocationInfo.mHash.Assign('#'); mLocationInfo.mHash.Append(NS_ConvertUTF16toUTF8(unicodeRef)); }
--- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -1235,16 +1235,23 @@ public: bool ServiceWorkersEnabled() const { AssertIsOnWorkerThread(); return mPreferences[WORKERPREF_SERVICEWORKERS]; } bool + InterceptionEnabled() const + { + AssertIsOnWorkerThread(); + return mPreferences[WORKERPREF_INTERCEPTION_ENABLED]; + } + + bool OnLine() const { AssertIsOnWorkerThread(); return mOnLine; } void StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult);
--- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -616,16 +616,26 @@ ServiceWorkerGlobalScope::SkipWaiting(Er aRv = NS_DispatchToMainThread(runnable); if (NS_WARN_IF(aRv.Failed())) { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } return promise.forget(); } +// static +bool +ServiceWorkerGlobalScope::InterceptionEnabled(JSContext* aCx, JSObject* aObj) +{ + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); + return worker->InterceptionEnabled(); +} + WorkerDebuggerGlobalScope::WorkerDebuggerGlobalScope( WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) { mWorkerPrivate->AssertIsOnWorkerThread(); } WorkerDebuggerGlobalScope::~WorkerDebuggerGlobalScope()
--- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -210,16 +210,19 @@ public: WorkerGlobalScope) ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsACString& aScope); virtual bool WrapGlobalObject(JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) override; + static bool + InterceptionEnabled(JSContext* aCx, JSObject* aObj); + void GetScope(nsString& aScope) const { aScope = mScope; } ServiceWorkerClients* Clients();
--- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -193,16 +193,17 @@ struct JSSettings } }; enum WorkerPreference { WORKERPREF_DUMP = 0, // browser.dom.window.dump.enabled WORKERPREF_DOM_CACHES, // dom.caches.enabled WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled + WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled WORKERPREF_COUNT }; // Implemented in WorkerPrivate.cpp struct WorkerLoadInfo { // All of these should be released in WorkerPrivateParent::ForgetMainThreadObjects.
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/interception_featuredetect.js @@ -0,0 +1,4 @@ +// Only succeeds if onfetch is available. +if (!("onfetch" in self)) { + throw new Error("Not capable of interception"); +}
--- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -124,73 +124,76 @@ support-files = sw_clients/refresher_compressed.html sw_clients/refresher_compressed.html^headers^ sw_clients/refresher_cached.html sw_clients/refresher_cached_compressed.html sw_clients/refresher_cached_compressed.html^headers^ strict_mode_error.js skip_waiting_installed_worker.js skip_waiting_scope/index.html + interception_featuredetect.js thirdparty/iframe1.html thirdparty/iframe2.html thirdparty/register.html thirdparty/unregister.html thirdparty/sw.js register_https.html gzip_redirect_worker.js sw_clients/navigator.html eval_worker.js test_eval_not_allowed.html^headers^ -[test_unregister.html] -[test_installation_simple.html] +[test_app_protocol.html] +[test_bug1151916.html] +[test_claim.html] +[test_claim_fetch.html] +[test_claim_oninstall.html] +[test_client_focus.html] +[test_close.html] +[test_controller.html] +[test_cross_origin_url_after_redirect.html] +[test_empty_serviceworker.html] +[test_eval_allowed.html] +[test_eval_not_allowed.html] [test_fetch_event.html] +[test_force_refresh.html] +[test_gzip_redirect.html] [test_https_fetch.html] [test_https_fetch_cloned_response.html] +[test_https_origin_after_redirect.html] +[test_https_origin_after_redirect_cached.html] [test_https_synth_fetch_from_cached_sw.html] +[test_importscript.html] +[test_install_event.html] +[test_installation_simple.html] +[test_interception_featuredetect.html] [test_match_all.html] [test_match_all_advanced.html] -[test_install_event.html] -[test_navigator.html] -[test_scopes.html] -[test_controller.html] -[test_workerUnregister.html] -[test_workerUpdate.html] -[test_post_message.html] -[test_post_message_advanced.html] -[test_post_message_source.html] +[test_match_all_client_id.html] [test_match_all_client_properties.html] -[test_close.html] -[test_serviceworker_interfaces.html] -[test_serviceworker_not_sharedworker.html] -[test_match_all_client_id.html] -[test_sandbox_intercept.html] -[test_request_context.html] -[test_importscript.html] -[test_client_focus.html] -[test_bug1151916.html] -[test_workerupdatefoundevent.html] -[test_empty_serviceworker.html] -[test_periodic_update.html] -[test_claim_oninstall.html] -[test_claim.html] -[test_periodic_https_update.html] -[test_sanitize.html] -[test_sanitize_domain.html] -[test_service_worker_allowed.html] -[test_app_protocol.html] -[test_third_party_iframes.html] -[test_claim_fetch.html] -[test_force_refresh.html] -[test_skip_waiting.html] -[test_strict_mode_error.html] -[test_cross_origin_url_after_redirect.html] +[test_navigator.html] [test_origin_after_redirect.html] [test_origin_after_redirect_cached.html] [test_origin_after_redirect_to_https.html] [test_origin_after_redirect_to_https_cached.html] -[test_https_origin_after_redirect.html] -[test_https_origin_after_redirect_cached.html] -[test_gzip_redirect.html] +[test_periodic_https_update.html] +[test_periodic_update.html] +[test_post_message.html] +[test_post_message_advanced.html] +[test_post_message_source.html] [test_register_base.html] [test_register_https_in_http.html] -[test_eval_allowed.html] -[test_eval_not_allowed.html] +[test_request_context.html] +skip-if = toolkit == 'android' # Bug 1163410 +[test_scopes.html] +[test_sandbox_intercept.html] +[test_sanitize.html] +[test_sanitize_domain.html] +[test_service_worker_allowed.html] +[test_serviceworker_interfaces.html] +[test_serviceworker_not_sharedworker.html] +[test_skip_waiting.html] +[test_strict_mode_error.html] +[test_third_party_iframes.html] +[test_unregister.html] +[test_workerUnregister.html] +[test_workerUpdate.html] +[test_workerupdatefoundevent.html]
--- a/dom/workers/test/serviceworkers/periodic_update_test.js +++ b/dom/workers/test/serviceworkers/periodic_update_test.js @@ -60,14 +60,15 @@ function unregisterSW() { return testFrame(gPrefix + "periodic/unregister.html"); } function runTheTest() { SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, function() { start(); }); }
--- a/dom/workers/test/serviceworkers/test_app_protocol.html +++ b/dom/workers/test/serviceworkers/test_app_protocol.html @@ -22,16 +22,17 @@ const appManifestURL = let gApp; function setup() { return new Promise((resolve, reject) => { SpecialPowers.setAllAppsLaunchable(true); SpecialPowers.pushPrefEnv({'set': [ ['dom.mozBrowserFramesEnabled', true], ['dom.serviceWorkers.exemptFromPerDomainMax', true], + ["dom.serviceWorkers.interception.enabled", true], ['dom.serviceWorkers.enabled', true], ['dom.serviceWorkers.testing.enabled', true], ['dom.caches.enabled', true], ]}, () => { SpecialPowers.pushPermissions([ { 'type': 'webapps-manage', 'allow': 1, 'context': document }, { 'type': 'browser', 'allow': 1, 'context': document }, { 'type': 'embed-apps', 'allow': 1, 'context': document }
--- a/dom/workers/test/serviceworkers/test_bug1151916.html +++ b/dom/workers/test/serviceworkers/test_bug1151916.html @@ -89,16 +89,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.caches.enabled", true], ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_claim.html +++ b/dom/workers/test/serviceworkers/test_claim.html @@ -157,16 +157,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_claim_fetch.html +++ b/dom/workers/test/serviceworkers/test_claim_fetch.html @@ -83,16 +83,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_claim_oninstall.html +++ b/dom/workers/test/serviceworkers/test_claim_oninstall.html @@ -58,16 +58,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_client_focus.html +++ b/dom/workers/test/serviceworkers/test_client_focus.html @@ -81,16 +81,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_close.html +++ b/dom/workers/test/serviceworkers/test_close.html @@ -49,16 +49,17 @@ } }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_controller.html +++ b/dom/workers/test/serviceworkers/test_controller.html @@ -69,16 +69,17 @@ ok(false, "Some test failed with error " + e); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html +++ b/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html @@ -35,16 +35,17 @@ } }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_empty_serviceworker.html +++ b/dom/workers/test/serviceworkers/test_empty_serviceworker.html @@ -31,16 +31,17 @@ SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_fetch_event.html +++ b/dom/workers/test/serviceworkers/test_fetch_event.html @@ -68,16 +68,17 @@ ok(false, "Some test failed with error " + e); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_force_refresh.html +++ b/dom/workers/test/serviceworkers/test_force_refresh.html @@ -69,16 +69,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.caches.enabled", true], ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_https_fetch.html +++ b/dom/workers/test/serviceworkers/test_https_fetch.html @@ -42,16 +42,17 @@ } }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.caches.enabled", true] ]}, runTest); }; </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html +++ b/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html @@ -40,16 +40,17 @@ } }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.caches.enabled", true] ]}, runTest); }; </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html +++ b/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html @@ -53,16 +53,17 @@ } }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.caches.enabled", true] ]}, runTest); }; </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/test_importscript.html +++ b/dom/workers/test/serviceworkers/test_importscript.html @@ -58,15 +58,16 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_install_event.html +++ b/dom/workers/test/serviceworkers/test_install_event.html @@ -98,16 +98,17 @@ ok(false, "Some test failed with error " + e); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_installation_simple.html +++ b/dom/workers/test/serviceworkers/test_installation_simple.html @@ -196,16 +196,17 @@ ok(false, "Some test failed with error " + e); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.messageChannel.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_interception_featuredetect.html @@ -0,0 +1,96 @@ + +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1173389 - Test fetch interception feature detection.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + function register() { + // Randomness required to prevent reusing existing registration. + return navigator.serviceWorker.register("interception_featuredetect.js", + { scope: "featuredetect/" + Math.random() }); + } + + function registerWithPrefDisabled() { + return new Promise(function(resolve, reject) { + SpecialPowers.pushPrefEnv( + {"set": [["dom.serviceWorkers.interception.enabled", false]]}, + function() { + register().then(function(v) { + ok(false, "Registration should fail when interception is disabled."); + reject(); + }, function(e) { + ok(true, "Registration should fail when interception is disabled."); + resolve(); + }); + }); + }); + } + + function registerWithPrefEnabled() { + return new Promise(function(resolve, reject) { + SpecialPowers.pushPrefEnv( + {"set": [["dom.serviceWorkers.interception.enabled", true]]}, + function() { + register().then(function(v) { + ok(true, "Registration should succeed when interception is enabled."); + resolve(); + }, function(e) { + ok(false, "Registration should succeed when interception is enabled."); + reject() + }); + }); + }); + } + + function unregister() { + return navigator.serviceWorker.getRegistrations().then(function(regs) { + var unregs = []; + regs.forEach(function(reg) { + if (reg.scope.indexOf("featuredetect") > -1) { + unregs.push(reg.unregister()); + } + }) + + return Promise.all(unregs); + }); + } + + function runTest() { + Promise.resolve() + .then(registerWithPrefDisabled) + .then(registerWithPrefEnabled) + .then(registerWithPrefDisabled) + .then(unregister) + // put more tests here. + .then(function() { + SimpleTest.finish(); + }).catch(function(e) { + ok(false, "Some test failed with error " + e); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", false], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] + ]}, runTest); +</script> +</pre> +</body> +</html> +
--- a/dom/workers/test/serviceworkers/test_match_all.html +++ b/dom/workers/test/serviceworkers/test_match_all.html @@ -65,16 +65,17 @@ ok(true, "Didn't crash on resolving matchAll promises while worker shuts down."); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_match_all_advanced.html +++ b/dom/workers/test/serviceworkers/test_match_all_advanced.html @@ -85,16 +85,17 @@ SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_match_all_client_id.html +++ b/dom/workers/test/serviceworkers/test_match_all_client_id.html @@ -76,15 +76,16 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_match_all_client_properties.html +++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html @@ -83,15 +83,16 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_navigator.html +++ b/dom/workers/test/serviceworkers/test_navigator.html @@ -23,16 +23,17 @@ ok(navigator.serviceWorker.ready instanceof Promise, "navigator.serviceWorker.ready should be a Promise."); ok(navigator.serviceWorker.controller === null, "There should be no controller worker for an uncontrolled document."); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true] ]}, function() { checkEnabled(); SimpleTest.finish(); }); </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/test_post_message.html +++ b/dom/workers/test/serviceworkers/test_post_message.html @@ -64,16 +64,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_post_message_advanced.html +++ b/dom/workers/test/serviceworkers/test_post_message_advanced.html @@ -93,16 +93,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_post_message_source.html +++ b/dom/workers/test/serviceworkers/test_post_message_source.html @@ -52,16 +52,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_request_context.html +++ b/dom/workers/test/serviceworkers/test_request_context.html @@ -83,16 +83,17 @@ SpecialPowers.pushPrefEnv({"set": [ ["beacon.enabled", true], ["browser.send_pings", true], ["browser.send_pings.max_per_link", -1], ["dom.caches.enabled", true], ["dom.image.picture.enabled", true], ["dom.image.srcset.enabled", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_sandbox_intercept.html +++ b/dom/workers/test/serviceworkers/test_sandbox_intercept.html @@ -35,16 +35,17 @@ } }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_sanitize.html +++ b/dom/workers/test/serviceworkers/test_sanitize.html @@ -70,16 +70,17 @@ function registerSW() { return testFrame("sanitize/register.html"); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, function() { start(); }); </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/test_sanitize_domain.html +++ b/dom/workers/test/serviceworkers/test_sanitize_domain.html @@ -73,16 +73,17 @@ return testFrame("http://prefixexample.com/tests/dom/workers/test/serviceworkers/sanitize/register.html"); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, function() { start(); }); </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/test_scopes.html +++ b/dom/workers/test/serviceworkers/test_scopes.html @@ -106,16 +106,17 @@ ok(false, "Some test failed with error " + e); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_service_worker_allowed.html +++ b/dom/workers/test/serviceworkers/test_service_worker_allowed.html @@ -62,13 +62,14 @@ }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html +++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html @@ -99,16 +99,17 @@ // if service workers are disabled by default we want to force on both // service workers and "dom.caches.enabled". But if service workers are // enabled by default, we do not want to mess with the "dom.caches.enabled" // value, since that would defeat the purpose of the test. Use a subframe // to decide whether service workers are enabled by default, so we don't // force creation of our own Navigator object before our prefs are set. var prefs = [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]; var subframe = document.createElement("iframe"); document.body.appendChild(subframe); if (!("serviceWorker" in subframe.contentWindow.navigator)) { prefs.push(["dom.caches.enabled", true]);
--- a/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html +++ b/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html @@ -51,16 +51,17 @@ sw.postMessage({msg: "whoareyou"}); }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_skip_waiting.html +++ b/dom/workers/test/serviceworkers/test_skip_waiting.html @@ -81,15 +81,16 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_strict_mode_error.html +++ b/dom/workers/test/serviceworkers/test_strict_mode_error.html @@ -24,16 +24,17 @@ SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_unregister.html +++ b/dom/workers/test/serviceworkers/test_unregister.html @@ -123,16 +123,17 @@ ok(false, "Some test failed with error " + e); SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_workerUnregister.html +++ b/dom/workers/test/serviceworkers/test_workerUnregister.html @@ -67,16 +67,17 @@ }).then(function() { SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_workerUpdate.html +++ b/dom/workers/test/serviceworkers/test_workerUpdate.html @@ -40,16 +40,17 @@ }).then(function() { SimpleTest.finish(); }); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html +++ b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html @@ -70,16 +70,17 @@ .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.interception.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ]}, runTest); </script> </pre> </body> </html>
--- a/dom/workers/test/urlApi_worker.js +++ b/dom/workers/test/urlApi_worker.js @@ -106,17 +106,17 @@ onmessage = function() { port: '', pathname: '/', search: '?test', hash: '' }, { url: 'http://example.com/carrot#question%3f', base: undefined, error: false, - hash: '#question?' + hash: '#question%3f' }, { url: 'https://example.com:4443?', base: undefined, error: false, protocol: 'https:', port: '4443', pathname: '/', hash: '',
--- a/dom/xbl/nsXBLMaybeCompiled.h +++ b/dom/xbl/nsXBLMaybeCompiled.h @@ -87,29 +87,29 @@ namespace js { template <class UncompiledT> struct GCMethods<nsXBLMaybeCompiled<UncompiledT> > { typedef struct GCMethods<JSObject *> Base; static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); } - static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function) - { - return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction()); - } - - static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp) + static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp, + nsXBLMaybeCompiled<UncompiledT> prev, + nsXBLMaybeCompiled<UncompiledT> next) { - Base::postBarrier(&functionp->UnsafeGetJSFunction()); - } - - static void relocate(nsXBLMaybeCompiled<UncompiledT>* functionp) - { - Base::relocate(&functionp->UnsafeGetJSFunction()); + if (next.IsCompiled()) { + Base::postBarrier(&functionp->UnsafeGetJSFunction(), + prev.IsCompiled() ? prev.UnsafeGetJSFunction() : nullptr, + next.UnsafeGetJSFunction()); + } else if (prev.IsCompiled()) { + Base::postBarrier(&prev.UnsafeGetJSFunction(), + prev.UnsafeGetJSFunction(), + nullptr); + } } }; template <class UncompiledT> class HeapBase<nsXBLMaybeCompiled<UncompiledT> > { const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >& wrapper() const { return *static_cast<const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >*>(this);
--- a/dom/xbl/nsXBLProtoImplField.cpp +++ b/dom/xbl/nsXBLProtoImplField.cpp @@ -240,22 +240,17 @@ FieldGetterImpl(JSContext *cx, JS::CallA return false; } if (!installed) { args.rval().setUndefined(); return true; } - JS::Rooted<JS::Value> v(cx); - if (!JS_GetPropertyById(cx, thisObj, id, &v)) { - return false; - } - args.rval().set(v); - return true; + return JS_GetPropertyById(cx, thisObj, id, args.rval()); } static bool FieldGetter(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldGetterImpl> (cx, args);
--- a/gfx/2d/Matrix.h +++ b/gfx/2d/Matrix.h @@ -8,16 +8,17 @@ #include "Types.h" #include "Rect.h" #include "Point.h" #include <iosfwd> #include <math.h> #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" +#include "mozilla/FloatingPoint.h" namespace mozilla { namespace gfx { class Quaternion; static bool FuzzyEqual(Float aV1, Float aV2) { // XXX - Check if fabs does the smart thing and just negates the sign bit. @@ -852,16 +853,36 @@ public: gfx::FuzzyEqual(_21, o._21) && gfx::FuzzyEqual(_22, o._22) && gfx::FuzzyEqual(_23, o._23) && gfx::FuzzyEqual(_24, o._24) && gfx::FuzzyEqual(_31, o._31) && gfx::FuzzyEqual(_32, o._32) && gfx::FuzzyEqual(_33, o._33) && gfx::FuzzyEqual(_34, o._34) && gfx::FuzzyEqual(_41, o._41) && gfx::FuzzyEqual(_42, o._42) && gfx::FuzzyEqual(_43, o._43) && gfx::FuzzyEqual(_44, o._44); } + bool FuzzyEqualsMultiplicative(const Matrix4x4& o) const + { + return ::mozilla::FuzzyEqualsMultiplicative(_11, o._11) && + ::mozilla::FuzzyEqualsMultiplicative(_12, o._12) && + ::mozilla::FuzzyEqualsMultiplicative(_13, o._13) && + ::mozilla::FuzzyEqualsMultiplicative(_14, o._14) && + ::mozilla::FuzzyEqualsMultiplicative(_21, o._21) && + ::mozilla::FuzzyEqualsMultiplicative(_22, o._22) && + ::mozilla::FuzzyEqualsMultiplicative(_23, o._23) && + ::mozilla::FuzzyEqualsMultiplicative(_24, o._24) && + ::mozilla::FuzzyEqualsMultiplicative(_31, o._31) && + ::mozilla::FuzzyEqualsMultiplicative(_32, o._32) && + ::mozilla::FuzzyEqualsMultiplicative(_33, o._33) && + ::mozilla::FuzzyEqualsMultiplicative(_34, o._34) && + ::mozilla::FuzzyEqualsMultiplicative(_41, o._41) && + ::mozilla::FuzzyEqualsMultiplicative(_42, o._42) && + ::mozilla::FuzzyEqualsMultiplicative(_43, o._43) && + ::mozilla::FuzzyEqualsMultiplicative(_44, o._44); + } + bool IsBackfaceVisible() const { // Inverse()._33 < 0; Float det = Determinant(); Float __33 = _12*_24*_41 - _14*_22*_41 + _14*_21*_42 - _11*_24*_42 - _12*_21*_44 + _11*_22*_44; return (__33 * det) < 0; @@ -884,16 +905,34 @@ public: static const float error = 1e-5f; NudgeToInteger(&_41, error); NudgeToInteger(&_42, error); NudgeToInteger(&_43, error); NudgeToInteger(&_44, error); return *this; } + // Nudge the 3D components to integer so that this matrix will become 2D if + // it's very close to already being 2D. + // This doesn't change the _41 and _42 components. + Matrix4x4 &NudgeTo2D() + { + NudgeToInteger(&_13); + NudgeToInteger(&_14); + NudgeToInteger(&_23); + NudgeToInteger(&_24); + NudgeToInteger(&_31); + NudgeToInteger(&_32); + NudgeToInteger(&_33); + NudgeToInteger(&_34); + NudgeToInteger(&_43); + NudgeToInteger(&_44); + return *this; + } + Point4D TransposedVector(int aIndex) const { MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); return Point4D(*((&_11)+aIndex), *((&_21)+aIndex), *((&_31)+aIndex), *((&_41)+aIndex)); } void SetTransposedVector(int aIndex, Point4D &aVector) {
new file mode 100644 --- /dev/null +++ b/gfx/docs/index.rst @@ -0,0 +1,9 @@ +======== +Graphics +======== + +The graphics team's documentation is currently using doxygen. We're tracking the work to integrate it better at https://bugzilla.mozilla.org/show_bug.cgi?id=1150232. + +For now you can read the graphics source code documentation here: + +http://people.mozilla.org/~bgirard/doxygen/gfx/
--- a/gfx/layers/LayerTreeInvalidation.cpp +++ b/gfx/layers/LayerTreeInvalidation.cpp @@ -131,17 +131,17 @@ struct LayerPropertiesBase : public Laye NotifySubDocInvalidationFunc aCallback, bool* aGeometryChanged); virtual void MoveBy(const IntPoint& aOffset); nsIntRegion ComputeChange(NotifySubDocInvalidationFunc aCallback, bool& aGeometryChanged) { - bool transformChanged = !mTransform.FuzzyEqual(mLayer->GetLocalTransform()) || + bool transformChanged = !mTransform.FuzzyEqualsMultiplicative(mLayer->GetLocalTransform()) || mLayer->GetPostXScale() != mPostXScale || mLayer->GetPostYScale() != mPostYScale; Layer* otherMask = mLayer->GetMaskLayer(); const Maybe<ParentLayerIntRect>& otherClip = mLayer->GetClipRect(); nsIntRegion result; if ((mMaskLayer ? mMaskLayer->mLayer : nullptr) != otherMask || (mUseClipRect != !!otherClip) || mLayer->GetLocalOpacity() != mOpacity ||
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -3118,16 +3118,17 @@ AsyncPanZoomController::HasReadyTouchBlo void AsyncPanZoomController::SetState(PanZoomState aNewState) { PanZoomState oldState; // Intentional scoping for mutex { ReentrantMonitorAutoEnter lock(mMonitor); + APZC_LOG("%p changing from state %d to %d\n", this, mState, aNewState); oldState = mState; mState = aNewState; } DispatchStateChangeNotification(oldState, aNewState); } void AsyncPanZoomController::DispatchStateChangeNotification(PanZoomState aOldState,
--- a/gfx/layers/apz/test/apz_test_native_event_utils.js +++ b/gfx/layers/apz/test/apz_test_native_event_utils.js @@ -119,8 +119,35 @@ function synthesizeNativeMouseMove(aElem function synthesizeNativeMouseMoveAndWaitForMoveEvent(aElement, aX, aY, aCallback) { var targetWindow = aElement.ownerDocument.defaultView; targetWindow.addEventListener("mousemove", function mousemoveWaiter(e) { targetWindow.removeEventListener("mousemove", mousemoveWaiter); setTimeout(aCallback, 0); }); return synthesizeNativeMouseMove(aElement, aX, aY); } + +// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels +// relative to the top-left of |aElement|'s bounding rect. +function synthesizeNativeTouch(aElement, aX, aY, aType, aObserver = null, aTouchId = 0) { + var targetWindow = aElement.ownerDocument.defaultView; + + var scale = targetWindow.devicePixelRatio; + var rect = aElement.getBoundingClientRect(); + var x = targetWindow.mozInnerScreenX + ((rect.left + aX) * scale); + var y = targetWindow.mozInnerScreenY + ((rect.top + aY) * scale); + + var utils = SpecialPowers.getDOMWindowUtils(targetWindow); + utils.sendNativeTouchPoint(aTouchId, aType, x, y, 1, 90, aObserver); + return true; +} + +function synthesizeNativeDrag(aElement, aX, aY, aDeltaX, aDeltaY, aObserver = null, aTouchId = 0) { + synthesizeNativeTouch(aElement, aX, aY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId); + var steps = Math.max(Math.abs(aDeltaX), Math.abs(aDeltaY)); + for (var i = 1; i < steps; i++) { + var dx = i * (aDeltaX / steps); + var dy = i * (aDeltaY / steps); + synthesizeNativeTouch(aElement, aX + dx, aY + dy, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId); + } + synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId); + return synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, aObserver, aTouchId); +}
--- a/gfx/layers/apz/test/apz_test_utils.js +++ b/gfx/layers/apz/test/apz_test_utils.js @@ -94,8 +94,25 @@ function buildApzcTree(paint) { if ("hasNoParentWithSameLayersId" in paint[scrollId]) { addRoot(root, scrollId); } else if ("parentScrollId" in paint[scrollId]) { addLink(root, scrollId, paint[scrollId]["parentScrollId"]); } } return root; } + +function flushApzRepaints(aCallback, aWindow = window) { + if (!aCallback) { + throw "A callback must be provided!"; + } + var repaintDone = function() { + SpecialPowers.Services.obs.removeObserver(repaintDone, "apz-repaints-flushed", false); + setTimeout(aCallback, 0); + }; + SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed", false); + if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) { + dump("Flushed APZ repaints, waiting for callback...\n"); + } else { + dump("Flushing APZ repaints was a no-op, triggering callback directly...\n"); + repaintDone(); + } +}
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/helper_basic_pan.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width; initial-scale=1.0"> + <title>Sanity panning test</title> + <script type="application/javascript" src="apz_test_native_event_utils.js"></script> + <script type="application/javascript" src="apz_test_utils.js"></script> + <script type="application/javascript"> + +function scrollPage() { + SpecialPowers._addMessageListener("APZ:TransformEnd", function() { + dump("Transform complete; flushing repaints...\n"); + flushApzRepaints(checkScroll); + }); + + const TOUCH_SLOP = 1; + synthesizeNativeDrag(document.body, 10, 100, 0, -(50 + TOUCH_SLOP)); + dump("Finished native drag, waiting for transform-end observer...\n"); +} + +function checkScroll() { + window.opener.is(window.scrollY, 50, "check that the window scrolled"); + window.opener.testDone(); +} + +window.onload = function() { + setTimeout(scrollPage, 0); +} + + </script> +</head> +<body> + <div style="height: 5000px; background-color: lightgreen;"> + This div makes the page scrollable. + </div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/helper_div_pan.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width; initial-scale=1.0"> + <title>Sanity panning test for scrollable div</title> + <script type="application/javascript" src="apz_test_native_event_utils.js"></script> + <script type="application/javascript" src="apz_test_utils.js"></script> + <script type="application/javascript"> + +function scrollOuter() { + SpecialPowers._addMessageListener("APZ:TransformEnd", function() { + dump("Transform complete; flushing repaints...\n"); + flushApzRepaints(checkScroll); + }); + + const TOUCH_SLOP = 1; + synthesizeNativeDrag(document.getElementById('outer'), 10, 100, 0, -(50 + TOUCH_SLOP)); + dump("Finished native drag, waiting for transform-end observer...\n"); +} + +function checkScroll() { + var outerScroll = document.getElementById('outer').scrollTop; + window.opener.is(outerScroll, 50, "check that the div scrolled"); + window.opener.testDone(); +} + +window.onload = function() { + setTimeout(scrollOuter, 0); +} + + </script> +</head> +<body> + <div id="outer" style="height: 250px; border: solid 1px black; overflow:scroll"> + <div style="height: 5000px; background-color: lightblue"> + This div makes the |outer| div scrollable. + </div> + </div> + <div style="height: 5000px; background-color: lightgreen;"> + This div makes the top-level page scrollable. + </div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/helper_iframe_pan.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width; initial-scale=1.0"> + <title>Sanity panning test for scrollable div</title> + <script type="application/javascript" src="apz_test_native_event_utils.js"></script> + <script type="application/javascript" src="apz_test_utils.js"></script> + <script type="application/javascript"> + +function scrollOuter() { + var outer = document.getElementById('outer'); + SpecialPowers._addMessageListener("APZ:TransformEnd", function() { + dump("Transform complete; flushing repaints...\n"); + flushApzRepaints(checkScroll, outer.contentWindow); + }); + + const TOUCH_SLOP = 1; + synthesizeNativeDrag(outer.contentDocument.body, 10, 100, 0, -(50 + TOUCH_SLOP)); + dump("Finished native drag, waiting for transform-end observer...\n"); +} + +function checkScroll() { + var outerScroll = document.getElementById('outer').contentWindow.scrollY; + window.opener.is(outerScroll, 50, "check that the iframe scrolled"); + window.opener.testDone(); +} + +window.onload = function() { + setTimeout(scrollOuter, 0); +} + + </script> +</head> +<body> + <iframe id="outer" style="height: 250px; border: solid 1px black" src="data:text/html,<body style='height:5000px'>"></iframe> + <div style="height: 5000px; background-color: lightgreen;"> + This div makes the top-level page scrollable. + </div> +</body> +</html>
--- a/gfx/layers/apz/test/mochitest.ini +++ b/gfx/layers/apz/test/mochitest.ini @@ -2,19 +2,24 @@ support-files = apz_test_utils.js apz_test_native_event_utils.js helper_bug982141.html helper_bug1151663.html helper_iframe1.html helper_iframe2.html helper_subframe_style.css + helper_basic_pan.html + helper_div_pan.html + helper_iframe_pan.html tags = apz [test_bug982141.html] skip-if = toolkit != 'gonk' # bug 991198 [test_bug1151663.html] skip-if = toolkit != 'gonk' # bug 991198 [test_wheel_scroll.html] skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet [test_bug1151667.html] skip-if = (os == 'android') || (os == 'b2g') # wheel events not supported on mobile [test_layerization.html] skip-if = (os == 'android') || (os == 'b2g') # uses wheel events which are not supported on mobile +[test_basic_pan.html] +skip-if = toolkit != 'gonk'
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/test_basic_pan.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Sanity panning test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.testInChaosMode(); + +// this page just serially loads each one of the following test helper pages in +// a new window and waits for it to call testDone() +var tests = [ + 'helper_basic_pan.html', + 'helper_div_pan.html', + 'helper_iframe_pan.html', +]; + +var testIndex = -1; +var w = null; + +function testDone() { + if (w) { + w.close(); + } + testIndex++; + if (testIndex < tests.length) { + w = window.open(tests[testIndex], "_blank"); + } else { + SimpleTest.finish(); + } +} + +window.onload = function() { + SpecialPowers.pushPrefEnv( + { "set": + [ + // Dropping the touch slop to 0 makes the tests easier to write because + // we can just do a one-pixel drag to get over the pan threshold rather + // than having to hard-code some larger value. + ["apz.touch_start_tolerance", "0.0"], + + // The B2G emulator is hella slow, and needs more than 300ms to run the + // main-thread code that deals with layerizing subframes and running + // touch listeners. In my local runs this needs to be at least 1000. + ["apz.content_response_timeout", "5000"] + ] + }, testDone); +}; + + </script> +</head> +<body> +</body> +</html>
--- a/gfx/layers/client/TextureClient.cpp +++ b/gfx/layers/client/TextureClient.cpp @@ -355,16 +355,17 @@ TextureClient::CreateForDrawing(ISurface aSize.width <= maxTextureSize && aSize.height <= maxTextureSize) { if (gfxWindowsPlatform::GetPlatform()->GetD3D9Device()) { texture = new CairoTextureClientD3D9(aAllocator, aFormat, aTextureFlags); } } if (!texture && aFormat == SurfaceFormat::B8G8R8X8 && + aAllocator->IsSameProcess() && aMoz2DBackend == gfx::BackendType::CAIRO) { if (aAllocator->IsSameProcess()) { texture = new TextureClientMemoryDIB(aAllocator, aFormat, aTextureFlags); } else { texture = new TextureClientShmemDIB(aAllocator, aFormat, aTextureFlags); } } #endif
--- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -136,22 +136,24 @@ AsyncCompositionManager::ComputeRotation { if (!mTargetConfig.naturalBounds().IsEmpty()) { mWorldTransform = ComputeTransformForRotation(mTargetConfig.naturalBounds(), mTargetConfig.rotation()); } } -static bool -GetBaseTransform2D(Layer* aLayer, Matrix* aTransform) +static void +GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform) { // Start with the animated transform if there is one - return (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() ? - aLayer->GetLocalTransform() : aLayer->GetTransform()).Is2D(aTransform); + *aTransform = + (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() + ? aLayer->GetLocalTransform() + : aLayer->GetTransform()); } static void TransformClipRect(Layer* aLayer, const Matrix4x4& aTransform) { const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect(); if (clipRect) { @@ -178,64 +180,56 @@ SetShadowTransform(Layer* aLayer, Matrix } aTransform.PostScale(1.0f / aLayer->GetPostXScale(), 1.0f / aLayer->GetPostYScale(), 1); aLayer->AsLayerComposite()->SetShadowTransform(aTransform); } static void -TranslateShadowLayer2D(Layer* aLayer, - const gfxPoint& aTranslation, - bool aAdjustClipRect) +TranslateShadowLayer(Layer* aLayer, + const gfxPoint& aTranslation, + bool aAdjustClipRect) { // This layer might also be a scrollable layer and have an async transform. // To make sure we don't clobber that, we start with the shadow transform. // (i.e. GetLocalTransform() instead of GetTransform()). // Note that the shadow transform is reset on every frame of composition so // we don't have to worry about the adjustments compounding over successive // frames. - Matrix layerTransform; - if (!aLayer->GetLocalTransform().Is2D(&layerTransform)) { - return; - } + Matrix4x4 layerTransform = aLayer->GetLocalTransform(); - // Apply the 2D translation to the layer transform. - layerTransform._31 += aTranslation.x; - layerTransform._32 += aTranslation.y; + // Apply the translation to the layer transform. + layerTransform.PostTranslate(aTranslation.x, aTranslation.y, 0); - SetShadowTransform(aLayer, Matrix4x4::From2D(layerTransform)); + SetShadowTransform(aLayer, layerTransform); aLayer->AsLayerComposite()->SetShadowTransformSetByAnimation(false); if (aAdjustClipRect) { TransformClipRect(aLayer, Matrix4x4::Translation(aTranslation.x, aTranslation.y, 0)); } // If a fixed- or sticky-position layer has a mask layer, that mask should // move along with the layer, so apply the translation to the mask layer too. if (Layer* maskLayer = aLayer->GetMaskLayer()) { - TranslateShadowLayer2D(maskLayer, aTranslation, false); + TranslateShadowLayer(maskLayer, aTranslation, false); } } -static bool -AccumulateLayerTransforms2D(Layer* aLayer, - Layer* aAncestor, - Matrix& aMatrix) +static void +AccumulateLayerTransforms(Layer* aLayer, + Layer* aAncestor, + Matrix4x4& aMatrix) { // Accumulate the transforms between this layer and the subtree root layer. for (Layer* l = aLayer; l && l != aAncestor; l = l->GetParent()) { - Matrix l2D; - if (!GetBaseTransform2D(l, &l2D)) { - return false; - } - aMatrix *= l2D; + Matrix4x4 transform; + GetBaseTransform(l, &transform); + aMatrix *= transform; } - - return true; } static LayerPoint GetLayerFixedMarginsOffset(Layer* aLayer, const LayerMargin& aFixedLayerMargins) { // Work out the necessary translation, in root scrollable layer space. // Because fixed layer margins are stored relative to the root scrollable @@ -307,47 +301,35 @@ AsyncCompositionManager::AlignFixedAndSt aCurrentTransformForRoot, aFixedLayerMargins); } } return; } // Insert a translation so that the position of the anchor point is the same // before and after the change to the transform of aTransformedSubtreeRoot. - // This currently only works for fixed layers with 2D transforms. // Accumulate the transforms between this layer and the subtree root layer. - Matrix ancestorTransform; - if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot, - ancestorTransform)) { - return; - } - - Matrix oldRootTransform; - Matrix newRootTransform; - if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) || - !aCurrentTransformForRoot.Is2D(&newRootTransform)) { - return; - } + Matrix4x4 ancestorTransform; + AccumulateLayerTransforms(aLayer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform); // Calculate the cumulative transforms between the subtree root with the // old transform and the current transform. - Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform; - Matrix newCumulativeTransform = ancestorTransform * newRootTransform; + Matrix4x4 oldCumulativeTransform = ancestorTransform * aPreviousTransformForRoot; + Matrix4x4 newCumulativeTransform = ancestorTransform * aCurrentTransformForRoot; if (newCumulativeTransform.IsSingular()) { return; } - Matrix newCumulativeTransformInverse = newCumulativeTransform.Inverse(); + Matrix4x4 newCumulativeTransformInverse = newCumulativeTransform.Inverse(); // Now work out the translation necessary to make sure the layer doesn't // move given the new sub-tree root transform. - Matrix layerTransform; - if (!GetBaseTransform2D(aLayer, &layerTransform)) { - return; - } + Matrix4x4 layerTransform; + GetBaseTransform(aLayer, &layerTransform); // Calculate any offset necessary, in previous transform sub-tree root // space. This is used to make sure fixed position content respects // content document fixed position margins. LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins); // Add the above offset to the anchor point so we can offset the layer by // and amount that's specified in old subtree layer space. @@ -382,25 +364,25 @@ AsyncCompositionManager::AlignFixedAndSt const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner(); translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) - IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost()); translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) - IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost()); } - // Finally, apply the 2D translation to the layer transform. Note that in + // Finally, apply the translation to the layer transform. Note that in // general we need to apply the same translation to the layer's clip rect, so // that the effective transform on the clip rect takes it back to where it was // originally, had there been no async scroll. In the case where the // fixed/sticky layer is the same as aTransformedSubtreeRoot, then the clip // rect is not affected by the scroll-induced async scroll transform anyway // (since the clip is applied post-transform) so we don't need to make the // adjustment. - TranslateShadowLayer2D(aLayer, ThebesPoint(translation), aLayer != aTransformedSubtreeRoot); + TranslateShadowLayer(aLayer, ThebesPoint(translation), aLayer != aTransformedSubtreeRoot); } static void SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart, StyleAnimationValue& aEnd, Animatable* aValue) { StyleAnimationValue interpolatedValue; NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
--- a/gfx/moz.build +++ b/gfx/moz.build @@ -29,8 +29,9 @@ DIRS += [ if CONFIG['MOZ_ENABLE_SKIA']: DIRS += ['skia'] if CONFIG['ENABLE_TESTS']: DIRS += ['tests/gtest'] TEST_DIRS += ['tests'] +SPHINX_TREES['gfx'] = 'docs'
--- a/gfx/thebes/gfxFcPlatformFontList.cpp +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -53,17 +53,16 @@ ToFcChar8Ptr(const char* aStr) static const char* ToCharPtr(const FcChar8 *aStr) { return reinterpret_cast<const char*>(aStr); } FT_Library gfxFcPlatformFontList::sCairoFTLibrary = nullptr; -FcChar8* gfxFcPlatformFontList::sSentinelFirstFamily = nullptr; static cairo_user_data_key_t sFcFontlistUserFontDataKey; // canonical name ==> first en name or first name if no en name // This is the required logic for fullname lookups as per CSS3 Fonts spec. static uint32_t FindCanonicalNameIndex(FcPattern* aFont, const char* aLangField) { @@ -1035,17 +1034,16 @@ gfxFcPlatformFontList::InitFontList() mLastConfig = FcConfigGetCurrent(); // reset font lists gfxPlatformFontList::InitFontList(); mLocalNames.Clear(); mGenericMappings.Clear(); mFcSubstituteCache.Clear(); - sSentinelFirstFamily = nullptr; // iterate over available fonts FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem); AddFontSetFamilies(systemFonts); #ifdef MOZ_BUNDLED_FONTS ActivateBundledFonts(); FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication); @@ -1248,39 +1246,39 @@ gfxFcPlatformFontList::FindFamily(const // actual font family found via this process. So check the cache first: NS_ConvertUTF16toUTF8 familyToFind(familyName); gfxFontFamily* cached = mFcSubstituteCache.GetWeak(familyToFind); if (cached) { return cached; } const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel"); - if (!sSentinelFirstFamily) { - nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate()); - FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName); - FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern); - FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sSentinelFirstFamily); - } + FcChar8* sentinelFirstFamily = nullptr; + nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate()); + FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName); + FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern); + FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily); // substitutions for font, -moz-sentinel pattern nsAutoRef<FcPattern> fontWithSentinel(FcPatternCreate()); - FcPatternAddString(fontWithSentinel, FC_FAMILY, ToFcChar8Ptr(familyToFind.get())); + FcPatternAddString(fontWithSentinel, FC_FAMILY, + ToFcChar8Ptr(familyToFind.get())); FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName); FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern); // iterate through substitutions until hitting the sentinel FcChar8* substName = nullptr; for (int i = 0; FcPatternGetString(fontWithSentinel, FC_FAMILY, i, &substName) == FcResultMatch; i++) { NS_ConvertUTF8toUTF16 subst(ToCharPtr(substName)); - if (sSentinelFirstFamily && - FcStrCmp(substName, sSentinelFirstFamily) == 0) { + if (sentinelFirstFamily && + FcStrCmp(substName, sentinelFirstFamily) == 0) { break; } gfxFontFamily* foundFamily = gfxPlatformFontList::FindFamily(subst); if (foundFamily) { // We've figured out what family the given name maps to, after any // fontconfig subsitutions. Cache it to speed up future lookups. mFcSubstituteCache.Put(familyToFind, foundFamily); return foundFamily;
--- a/gfx/thebes/gfxFcPlatformFontList.h +++ b/gfx/thebes/gfxFcPlatformFontList.h @@ -258,12 +258,11 @@ protected: // caching family lookups as found by FindFamily after resolving substitutions nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> mFcSubstituteCache; nsCOMPtr<nsITimer> mCheckFontUpdatesTimer; nsCountedRef<FcConfig> mLastConfig; static FT_Library sCairoFTLibrary; - static FcChar8* sSentinelFirstFamily; }; #endif /* GFXPLATFORMFONTLIST_H_ */
--- a/gfx/thebes/gfxTextRun.cpp +++ b/gfx/thebes/gfxTextRun.cpp @@ -595,31 +595,16 @@ gfxTextRun::Draw(gfxContext *aContext, g aContext, aProvider); *aAdvanceWidth = metrics.mAdvanceWidth * direction; } // return without drawing return; } - // Set up parameters that will be constant across all glyph runs we need - // to draw, regardless of the font used. - TextRunDrawParams params; - params.context = aContext; - params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); - params.isVerticalRun = IsVertical(); - params.isRTL = IsRightToLeft(); - params.direction = direction; - params.drawMode = aDrawMode; - params.callbacks = aCallbacks; - params.runContextPaint = aContextPaint; - params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; - params.dt = aContext->GetDrawTarget(); - params.fontSmoothingBGColor = aContext->GetFontSmoothingBackgroundColor(); - // synthetic bolding draws glyphs twice ==> colors with opacity won't draw // correctly unless first drawn without alpha BufferAlphaColor syntheticBoldBuffer(aContext); gfxRGBA currentColor; bool needToRestore = false; if (aDrawMode == DrawMode::GLYPH_FILL && HasNonOpaqueColor(aContext, currentColor) && @@ -629,16 +614,31 @@ gfxTextRun::Draw(gfxContext *aContext, g gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, gfxFont::LOOSE_INK_EXTENTS, aContext, aProvider); metrics.mBoundingBox.MoveBy(aPt); syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, GetAppUnitsPerDevUnit()); } + // Set up parameters that will be constant across all glyph runs we need + // to draw, regardless of the font used. + TextRunDrawParams params; + params.context = aContext; + params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); + params.isVerticalRun = IsVertical(); + params.isRTL = IsRightToLeft(); + params.direction = direction; + params.drawMode = aDrawMode; + params.callbacks = aCallbacks; + params.runContextPaint = aContextPaint; + params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; + params.dt = aContext->GetDrawTarget(); + params.fontSmoothingBGColor = aContext->GetFontSmoothingBackgroundColor(); + GlyphRunIterator iter(this, aStart, aLength); gfxFloat advance = 0.0; while (iter.NextRun()) { gfxFont *font = iter.GetGlyphRun()->mFont; uint32_t start = iter.GetStringStart(); uint32_t end = iter.GetStringEnd(); uint32_t ligatureRunStart = start;
--- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -78,19 +78,18 @@ Decoder::~Decoder() */ void Decoder::Init() { // No re-initializing MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!"); - // Fire OnStartDecode at init time to support bug 512435. if (!IsSizeDecode()) { - mProgress |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED; + mProgress |= FLAG_DECODE_STARTED; } // Implementation-specific initialization InitInternal(); mInitialized = true; } @@ -268,17 +267,17 @@ Decoder::CompleteDecode() if (mInFrame) { PostFrameStop(); } PostDecodeDone(); } else { // We're not usable. Record some final progress indicating the error. if (!IsSizeDecode()) { - mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED; + mProgress |= FLAG_DECODE_COMPLETE; } mProgress |= FLAG_HAS_ERROR; } } } void Decoder::Finish() @@ -619,17 +618,17 @@ Decoder::PostFrameStop(Opacity aFrameOpa MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one"); MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one"); // Update our state mInFrame = false; mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod); - mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED; + mProgress |= FLAG_FRAME_COMPLETE; // If we're not sending partial invalidations, then we send an invalidation // here when the first frame is complete. if (!mSendPartialInvalidations && !mIsAnimated) { mInvalidRect.UnionRect(mInvalidRect, gfx::IntRect(gfx::IntPoint(0, 0), GetSize())); } }
--- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -1206,21 +1206,19 @@ RasterImage::OnImageDataComplete(nsIRequ Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus); if (mDecodeOnlyOnDraw) { // For decode-only-on-draw images, we want to send notifications as if we've // already finished decoding. Otherwise some observers will never even try // to draw. (We may have already sent some of these notifications from // NotifyForDecodeOnlyOnDraw(), but ProgressTracker will ensure no duplicate // notifications get sent.) - loadProgress |= FLAG_ONLOAD_BLOCKED | - FLAG_DECODE_STARTED | + loadProgress |= FLAG_DECODE_STARTED | FLAG_FRAME_COMPLETE | - FLAG_DECODE_COMPLETE | - FLAG_ONLOAD_UNBLOCKED; + FLAG_DECODE_COMPLETE; } // Notify our listeners, which will fire this image's load event. NotifyProgress(loadProgress); return finalStatus; } @@ -1229,17 +1227,17 @@ RasterImage::NotifyForDecodeOnlyOnDraw() { if (!NS_IsMainThread()) { nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableMethod(this, &RasterImage::NotifyForDecodeOnlyOnDraw); NS_DispatchToMainThread(runnable); return; } - NotifyProgress(FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED); + NotifyProgress(FLAG_DECODE_STARTED); } nsresult RasterImage::OnImageDataAvailable(nsIRequest*, nsISupports*, nsIInputStream* aInStr, uint64_t aOffset, uint32_t aCount)
--- a/image/test/browser/browser_bug666317.js +++ b/image/test/browser/browser_bug666317.js @@ -36,16 +36,33 @@ function isImgDecoded() { function forceDecodeImg() { let doc = gBrowser.getBrowserForTab(newTab).contentWindow.document; let img = doc.getElementById('testImg'); let canvas = doc.createElement('canvas'); let ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); } +function runAfterAsyncEvents(aCallback) { + function handlePostMessage(aEvent) { + if (aEvent.data == 'next') { + window.removeEventListener('message', handlePostMessage, false); + aCallback(); + } + } + + window.addEventListener('message', handlePostMessage, false); + + // We'll receive the 'message' event after everything else that's currently in + // the event queue (which is a stronger guarantee than setTimeout, because + // setTimeout events may be coalesced). This lets us ensure that we run + // aCallback *after* any asynchronous events are delivered. + window.postMessage('next', '*'); +} + function test() { // Enable the discarding pref. oldDiscardingPref = prefBranch.getBoolPref('discardable'); prefBranch.setBoolPref('discardable', true); // Create and focus a new tab. oldTab = gBrowser.selectedTab; newTab = gBrowser.addTab('data:text/html,' + pageSource); @@ -67,37 +84,38 @@ function step2() { .createScriptedObserver(observer); // Clone the current imgIRequest with our new observer. var request = currentRequest(); var clonedRequest = request.clone(scriptedObserver); // Check that the image is decoded. forceDecodeImg(); + + // The FRAME_COMPLETE notification is delivered asynchronously, so continue + // after we're sure it has been delivered. + runAfterAsyncEvents(() => step3(result, scriptedObserver, clonedRequest)); +} + +function step3(result, scriptedObserver, clonedRequest) { ok(isImgDecoded(), 'Image should initially be decoded.'); // Focus the old tab, then fire a memory-pressure notification. This should // cause the decoded image in the new tab to be discarded. gBrowser.selectedTab = oldTab; var os = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); os.notifyObservers(null, 'memory-pressure', 'heap-minimize'); - // The discard notification is delivered asynchronously, so pump the event - // loop before checking. - window.addEventListener('message', function (event) { - if (event.data == 'step3') { - step3(result, scriptedObserver, clonedRequest); - } - }, false); - - window.postMessage('step3', '*'); + // The DISCARD notification is delivered asynchronously, so continue after + // we're sure it has been delivered. + runAfterAsyncEvents(() => step4(result, scriptedObserver, clonedRequest)); } -function step3(result, scriptedObserver, clonedRequest) { +function step4(result, scriptedObserver, clonedRequest) { ok(result.wasDiscarded, 'Image should be discarded.'); // And we're done. gBrowser.removeTab(newTab); prefBranch.setBoolPref('discardable', oldDiscardingPref); clonedRequest.cancelAndForgetObserver(0); finish(); }
--- a/image/test/mochitest/mochitest.ini +++ b/image/test/mochitest/mochitest.ini @@ -65,17 +65,16 @@ support-files = [test_bug468160.html] # [test_bug478398.html] # disabled - See bug 579139 [test_bug490949.html] skip-if = (toolkit == 'android' && processor == 'x86') #x86 only [test_bug496292.html] [test_bug497665.html] skip-if = (toolkit == 'android' && processor == 'x86') #x86 only -[test_bug512435.html] [test_bug552605-1.html] [test_bug552605-2.html] [test_bug553982.html] [test_bug601470.html] skip-if = (toolkit == 'android' && processor == 'x86') #x86 only [test_bug614392.html] [test_bug657191.html] [test_bug671906.html]
deleted file mode 100644 --- a/image/test/mochitest/test_bug512435.html +++ /dev/null @@ -1,49 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=512435 ---> -<head> - <title>Test for Bug 512435</title> - <script type="application/javascript" src="/MochiKit/MochiKit.js"></script> - <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <script type="text/javascript" src="/tests/image/test/mochitest/imgutils.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> -</head> -<body> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=512435">Mozilla Bug 512435</a> -<img id="img_a"> -<img id="img_b"> -</div> -<pre id="test"> -<script type="application/javascript"> - -// Boilerplate -const Ci = SpecialPowers.Ci; -const Cc = SpecialPowers.Cc; -SimpleTest.waitForExplicitFinish(); - -// We're relying on very particular behavior for certain images - clear the -// image cache, _then_ set src -clearImageCache(); -document.getElementById("img_a").src = "damon.jpg"; -document.getElementById("img_b").src = "shaver.png"; - -// Our handler -function loadHandler() { - - // The two images should be decoded - ok(isFrameDecoded("img_a"), "img_a should be decoded before onload fires"); - ok(isFrameDecoded("img_b"), "img_b should be decoded before onload fires"); - - // All done - SimpleTest.finish(); -} - -// Set our onload handler -window.onload = loadHandler; - -</script> -</pre> -</body> -</html>
--- a/js/public/Id.h +++ b/js/public/Id.h @@ -166,19 +166,17 @@ extern JS_PUBLIC_DATA(const jsid) JSID_E extern JS_PUBLIC_DATA(const JS::HandleId) JSID_VOIDHANDLE; extern JS_PUBLIC_DATA(const JS::HandleId) JSID_EMPTYHANDLE; namespace js { template <> struct GCMethods<jsid> { static jsid initial() { return JSID_VOID; } - static bool needsPostBarrier(jsid id) { return false; } - static void postBarrier(jsid* idp) {} - static void relocate(jsid* idp) {} + static void postBarrier(jsid* idp, jsid prev, jsid next) {} }; // If the jsid is a GC pointer type, convert to that type and call |f| with // the pointer. If the jsid is not a GC type, calls F::defaultValue. template <typename F, typename... Args> auto DispatchIdTyped(F f, jsid& id, Args&&... args) -> decltype(f(static_cast<JSString*>(nullptr), mozilla::Forward<Args>(args)...))
--- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -169,18 +169,17 @@ struct PersistentRootedMarker; namespace JS { template <typename T> class Rooted; template <typename T> class PersistentRooted; /* This is exposing internal state of the GC for inlining purposes. */ JS_FRIEND_API(bool) isGCEnabled(); -JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp); -JS_FRIEND_API(void) HeapObjectRelocate(JSObject** objp); +JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next); #ifdef JS_DEBUG /* * For generational GC, assert that an object is in the tenured generation as * opposed to being in the nursery. */ extern JS_FRIEND_API(void) AssertGCThingMustBeTenured(JSObject* obj); @@ -227,18 +226,17 @@ class Heap : public js::HeapBase<T> * For Heap, move semantics are equivalent to copy semantics. In C++, a * copy constructor taking const-ref is the way to get a single function * that will be used for both lvalue and rvalue copies, so we can simply * omit the rvalue variant. */ explicit Heap(const Heap<T>& p) { init(p.ptr); } ~Heap() { - if (js::GCMethods<T>::needsPostBarrier(ptr)) - relocate(); + post(ptr, js::GCMethods<T>::initial()); } DECLARE_POINTER_CONSTREF_OPS(T); DECLARE_POINTER_ASSIGN_OPS(Heap, T); DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); T* unsafeGet() { return &ptr; } @@ -252,39 +250,27 @@ class Heap : public js::HeapBase<T> bool isSetToCrashOnTouch() { return ptr == crashOnTouchPointer; } private: void init(T newPtr) { ptr = newPtr; - if (js::GCMethods<T>::needsPostBarrier(ptr)) - post(); + post(js::GCMethods<T>::initial(), ptr); } void set(T newPtr) { - if (js::GCMethods<T>::needsPostBarrier(newPtr)) { - ptr = newPtr; - post(); - } else if (js::GCMethods<T>::needsPostBarrier(ptr)) { - relocate(); /* Called before overwriting ptr. */ - ptr = newPtr; - } else { - ptr = newPtr; - } + T tmp = ptr; + ptr = newPtr; + post(tmp, ptr); } - void post() { - MOZ_ASSERT(js::GCMethods<T>::needsPostBarrier(ptr)); - js::GCMethods<T>::postBarrier(&ptr); - } - - void relocate() { - js::GCMethods<T>::relocate(&ptr); + void post(const T& prev, const T& next) { + js::GCMethods<T>::postBarrier(&ptr, prev, next); } enum { crashOnTouchPointer = 1 }; T ptr; }; @@ -599,57 +585,46 @@ struct RootKind<T*> { static ThingRootKind rootKind() { return T::rootKind(); } }; template <typename T> struct GCMethods<T*> { static T* initial() { return nullptr; } - static bool needsPostBarrier(T* v) { return false; } - static void postBarrier(T** vp) { - if (vp) - JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(vp)); + static void postBarrier(T** vp, T* prev, T* next) { + if (next) + JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(next)); } static void relocate(T** vp) {} }; template <> struct GCMethods<JSObject*> { static JSObject* initial() { return nullptr; } static gc::Cell* asGCThingOrNull(JSObject* v) { if (!v) return nullptr; MOZ_ASSERT(uintptr_t(v) > 32); return reinterpret_cast<gc::Cell*>(v); } - static bool needsPostBarrier(JSObject* v) { - return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v)); - } - static void postBarrier(JSObject** vp) { - JS::HeapObjectPostBarrier(vp); - } - static void relocate(JSObject** vp) { - JS::HeapObjectRelocate(vp); + static void postBarrier(JSObject** vp, JSObject* prev, JSObject* next) { + JS::HeapObjectPostBarrier(vp, prev, next); } }; template <> struct GCMethods<JSFunction*> { static JSFunction* initial() { return nullptr; } - static bool needsPostBarrier(JSFunction* v) { - return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v)); - } - static void postBarrier(JSFunction** vp) { - JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp)); - } - static void relocate(JSFunction** vp) { - JS::HeapObjectRelocate(reinterpret_cast<JSObject**>(vp)); + static void postBarrier(JSFunction** vp, JSFunction* prev, JSFunction* next) { + JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp), + reinterpret_cast<JSObject*>(prev), + reinterpret_cast<JSObject*>(next)); } }; } /* namespace js */ namespace JS { /*
--- a/js/public/Value.h +++ b/js/public/Value.h @@ -1624,38 +1624,35 @@ SameType(const Value& lhs, const Value& return JSVAL_SAME_TYPE_IMPL(lhs.data, rhs.data); } } // namespace JS /************************************************************************/ namespace JS { -JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep); -JS_PUBLIC_API(void) HeapValueRelocate(Value* valuep); +JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep, const Value& prev, const Value& next); } namespace js { template <> struct GCMethods<const JS::Value> { static JS::Value initial() { return JS::UndefinedValue(); } }; template <> struct GCMethods<JS::Value> { static JS::Value initial() { return JS::UndefinedValue(); } static gc::Cell* asGCThingOrNull(const JS::Value& v) { return v.isMarkable() ? v.toGCThing() : nullptr; } - static bool needsPostBarrier(const JS::Value& v) { - return v.isObject() && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(&v.toObject())); + static void postBarrier(JS::Value* v, const JS::Value& prev, const JS::Value& next) { + JS::HeapValuePostBarrier(v, prev, next); } - static void postBarrier(JS::Value* v) { JS::HeapValuePostBarrier(v); } - static void relocate(JS::Value* v) { JS::HeapValueRelocate(v); } }; template <class Outer> class MutableValueOperations; /* * A class designed for CRTP use in implementing the non-mutating parts of the * Value interface in Value-like classes. Outer must be a class inheriting * ValueOperations<Outer> with a visible extract() method returning the
--- a/js/src/asmjs/AsmJSModule.h +++ b/js/src/asmjs/AsmJSModule.h @@ -846,17 +846,17 @@ class AsmJSModule ScriptSource * scriptSource_; PropertyName * globalArgumentName_; PropertyName * importArgumentName_; PropertyName * bufferArgumentName_; uint8_t * code_; uint8_t * interruptExit_; uint8_t * outOfBoundsExit_; StaticLinkData staticLinkData_; - RelocatablePtrArrayBufferObjectMaybeShared maybeHeap_; + HeapPtrArrayBufferObjectMaybeShared maybeHeap_; AsmJSModule ** prevLinked_; AsmJSModule * nextLinked_; bool dynamicallyLinked_; bool loadedFromCache_; bool profilingEnabled_; bool interrupted_; void restoreHeapToInitialState(ArrayBufferObjectMaybeShared* maybePrevBuffer);
--- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -207,28 +207,16 @@ TryEvalJSON(JSContext* cx, JSLinearStrin if (!linearChars.init(cx, str)) return EvalJSON_Failure; return linearChars.isLatin1() ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval) : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval); } -static bool -HasPollutedScopeChain(JSObject* scopeChain) -{ - while (scopeChain) { - if (scopeChain->is<DynamicWithObject>()) - return true; - scopeChain = scopeChain->enclosingScope(); - } - - return false; -} - // Define subset of ExecuteType so that casting performs the injection. enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL }; // Common code implementing direct and indirect eval. // // Evaluate call.argv[2], if it is a string, in the context of the given calling // frame, with the provided scope chain, with the semantics of either a direct // or indirect eval (see ES5 10.4.2). If this is an indirect eval, scopeobj @@ -327,23 +315,18 @@ EvalKernel(JSContext* cx, const CallArgs RootedObject enclosing(cx); if (evalType == DIRECT_EVAL) enclosing = callerScript->innermostStaticScope(pc); Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing)); if (!staticScope) return false; - bool hasPollutedGlobalScope = - HasPollutedScopeChain(scopeobj) || - (evalType == DIRECT_EVAL && callerScript->hasPollutedGlobalScope()); - CompileOptions options(cx); options.setFileAndLine(filename, 1) - .setHasPollutedScope(hasPollutedGlobalScope) .setIsRunOnce(true) .setForEval(true) .setNoScriptRval(false) .setMutedErrors(mutedErrors) .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset) .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc)); AutoStableStringChars linearChars(cx); @@ -351,17 +334,17 @@ EvalKernel(JSContext* cx, const CallArgs return false; const char16_t* chars = linearChars.twoByteRange().start().get(); SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; SourceBufferHolder srcBuf(chars, linearStr->length(), ownership); JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), - scopeobj, callerScript, staticScope, + scopeobj, staticScope, callerScript, options, srcBuf, linearStr, staticLevel); if (!compiled) return false; if (compiled->strict()) staticScope->setStrict(); esg.setNewScript(compiled); @@ -405,31 +388,29 @@ js::DirectEvalStringFromIon(JSContext* c if (!esg.foundScript()) { RootedScript maybeScript(cx); const char* filename; unsigned lineno; bool mutedErrors; uint32_t pcOffset; DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset, - &mutedErrors, CALLED_FROM_JSOP_EVAL); + &mutedErrors, CALLED_FROM_JSOP_EVAL); const char* introducerFilename = filename; if (maybeScript && maybeScript->scriptSource()->introducerFilename()) introducerFilename = maybeScript->scriptSource()->introducerFilename(); RootedObject enclosing(cx, callerScript->innermostStaticScope(pc)); Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing)); if (!staticScope) return false; CompileOptions options(cx); options.setFileAndLine(filename, 1) - .setHasPollutedScope(HasPollutedScopeChain(scopeobj) || - callerScript->hasPollutedGlobalScope()) .setIsRunOnce(true) .setForEval(true) .setNoScriptRval(false) .setMutedErrors(mutedErrors) .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset) .maybeMakeStrictMode(IsStrictEvalPC(pc)); AutoStableStringChars linearChars(cx); @@ -437,17 +418,17 @@ js::DirectEvalStringFromIon(JSContext* c return false; const char16_t* chars = linearChars.twoByteRange().start().get(); SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; SourceBufferHolder srcBuf(chars, linearStr->length(), ownership); JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), - scopeobj, callerScript, staticScope, + scopeobj, staticScope, callerScript, options, srcBuf, linearStr, staticLevel); if (!compiled) return false; if (compiled->strict()) staticScope->setStrict(); esg.setNewScript(compiled); @@ -508,45 +489,41 @@ js::IsAnyBuiltinEval(JSFunction* fun) JS_FRIEND_API(bool) js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg, MutableHandleObject scopeArg) { CHECK_REQUEST(cx); assertSameCompartment(cx, global); MOZ_ASSERT(global->is<GlobalObject>()); - MOZ_RELEASE_ASSERT(scriptArg->hasPollutedGlobalScope()); + MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope()); RootedScript script(cx, scriptArg); if (script->compartment() != cx->compartment()) { - script = CloneScript(cx, nullptr, nullptr, script); + Rooted<ScopeObject*> staticScope(cx, StaticNonSyntacticScopeObjects::create(cx, nullptr)); + if (!staticScope) + return false; + script = CloneGlobalScript(cx, staticScope, script); if (!script) return false; Debugger::onNewScript(cx, script); } - RootedObject scope(cx, JS_NewPlainObject(cx)); + Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>()); + Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalRoot)); if (!scope) return false; - if (!scope->setQualifiedVarObj(cx)) - return false; - - if (!scope->setUnqualifiedVarObj(cx)) - return false; - JSObject* thisobj = GetThisObject(cx, global); if (!thisobj) return false; RootedValue thisv(cx, ObjectValue(*thisobj)); RootedValue rval(cx); - // XXXbz when this is fixed to pass in an actual ScopeObject, fix - // up the assert in js::CloneFunctionObject accordingly. if (!ExecuteKernel(cx, script, *scope, thisv, UndefinedValue(), EXECUTE_GLOBAL, NullFramePtr() /* evalInFrame */, rval.address())) { return false; } scopeArg.set(scope); return true;
--- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -1194,17 +1194,17 @@ MapObject::set(JSContext* cx, HandleObje ValueMap* map = obj->as<MapObject>().getData(); if (!map) return false; AutoHashableValueRooter key(cx); if (!key.setValue(cx, k)) return false; - RelocatableValue rval(v); + HeapValue rval(v); if (!map->put(key, rval)) { ReportOutOfMemory(cx); return false; } WriteBarrierPost(cx->runtime(), map, key.get()); return true; } @@ -1289,17 +1289,17 @@ MapObject::construct(JSContext* cx, unsi RootedValue val(cx); if (!GetElement(cx, pairObj, pairObj, 1, &val)) return false; if (isOriginalAdder) { if (!hkey.setValue(cx, key)) return false; - RelocatableValue rval(val); + HeapValue rval(val); if (!map->put(hkey, rval)) { ReportOutOfMemory(cx); return false; } WriteBarrierPost(cx->runtime(), map, key); } else { if (!args2.init(2)) return false; @@ -1446,17 +1446,17 @@ MapObject::has(JSContext* cx, unsigned a bool MapObject::set_impl(JSContext* cx, CallArgs args) { MOZ_ASSERT(MapObject::is(args.thisv())); ValueMap& map = extract(args); ARG0_KEY(cx, args, key); - RelocatableValue rval(args.get(1)); + HeapValue rval(args.get(1)); if (!map.put(key, rval)) { ReportOutOfMemory(cx); return false; } WriteBarrierPost(cx->runtime(), &map, key.get()); args.rval().set(args.thisv()); return true; } @@ -1467,24 +1467,24 @@ MapObject::set(JSContext* cx, unsigned a CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args); } bool MapObject::delete_impl(JSContext* cx, CallArgs args) { // MapObject::mark does not mark deleted entries. Incremental GC therefore - // requires that no RelocatableValue objects pointing to heap values be - // left alive in the ValueMap. + // requires that no HeapValue objects pointing to heap values be left alive + // in the ValueMap. // // OrderedHashMap::remove() doesn't destroy the removed entry. It merely // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because // makeEmpty clears the value by doing e->value = Value(), and in the case - // of a ValueMap, Value() means RelocatableValue(), which is the same as - // RelocatableValue(UndefinedValue()). + // of a ValueMap, Value() means HeapValue(), which is the same as + // HeapValue(UndefinedValue()). MOZ_ASSERT(MapObject::is(args.thisv())); ValueMap& map = extract(args); ARG0_KEY(cx, args, key); bool found; if (!map.remove(key, &found)) { ReportOutOfMemory(cx); return false;
--- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -72,17 +72,17 @@ class AutoHashableValueRooter : private template <class Key, class Value, class OrderedHashPolicy, class AllocPolicy> class OrderedHashMap; template <class T, class OrderedHashPolicy, class AllocPolicy> class OrderedHashSet; typedef OrderedHashMap<HashableValue, - RelocatableValue, + HeapValue, HashableValue::Hasher, RuntimeAllocPolicy> ValueMap; typedef OrderedHashSet<HashableValue, HashableValue::Hasher, RuntimeAllocPolicy> ValueSet; class MapObject : public NativeObject {
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2267,21 +2267,20 @@ EvalReturningScope(JSContext* cx, unsign JS::AutoFilename filename; unsigned lineno; DescribeScriptedCaller(cx, &filename, &lineno); JS::CompileOptions options(cx); options.setFileAndLine(filename.get(), lineno); options.setNoScriptRval(true); - options.setHasPollutedScope(true); JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); RootedScript script(cx); - if (!JS::Compile(cx, options, srcBuf, &script)) + if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) return false; if (global) { global = CheckedUnwrap(global); if (!global) { JS_ReportError(cx, "Permission denied to access global"); return false; }
--- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -139,20 +139,21 @@ MaybeCheckEvalFreeVariables(ExclusiveCon scope = scope->enclosingScope(); } } return true; } static inline bool -CanLazilyParse(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) +CanLazilyParse(ExclusiveContext* cx, HandleObject staticScope, + const ReadOnlyCompileOptions& options) { return options.canLazilyParse && - !options.hasPollutedGlobalScope && + !HasNonSyntacticStaticScopeChain(staticScope) && !cx->compartment()->options().disableLazyParsing() && !cx->compartment()->options().discardSource() && !options.sourceIsLazy; } static void MarkFunctionsWithinEvalScript(JSScript* script) { @@ -205,18 +206,18 @@ frontend::CreateScriptSourceObject(Exclu return nullptr; } return sso; } JSScript* frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain, + Handle<ScopeObject*> enclosingStaticScope, HandleScript evalCaller, - Handle<StaticEvalObject*> evalStaticScope, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, JSString* source_ /* = nullptr */, unsigned staticLevel /* = 0 */, SourceCompressionTask* extraSct /* = nullptr */) { MOZ_ASSERT(srcBuf.get()); @@ -255,17 +256,17 @@ frontend::CompileScript(ExclusiveContext if (!cx->compartment()->options().discardSource()) { if (options.sourceIsLazy) ss->setSourceRetrievable(); else if (!ss->setSourceCopy(cx, srcBuf, false, sct)) return nullptr; } - bool canLazilyParse = CanLazilyParse(cx, options); + bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.emplace(cx, alloc, options, srcBuf.get(), srcBuf.length(), /* foldConstants = */ false, (Parser<SyntaxParseHandler>*) nullptr, (LazyScript*) nullptr); @@ -279,32 +280,32 @@ frontend::CompileScript(ExclusiveContext parser.sct = sct; parser.ss = ss; if (!parser.checkOptions()) return nullptr; bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction(); Directives directives(options.strictOption); - GlobalSharedContext globalsc(cx, directives, evalStaticScope, options.extraWarningsOption); + GlobalSharedContext globalsc(cx, directives, enclosingStaticScope, options.extraWarningsOption); - Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun, + Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingStaticScope, savedCallerFun, options, staticLevel, sourceObject, 0, srcBuf.length())); if (!script) return nullptr; bool insideNonGlobalEval = - evalStaticScope && evalStaticScope->enclosingScopeForStaticScopeIter(); + enclosingStaticScope && enclosingStaticScope->is<StaticEvalObject>() && + enclosingStaticScope->as<StaticEvalObject>().enclosingScopeForStaticScopeIter(); BytecodeEmitter::EmitterMode emitterMode = options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, /* lazyScript = */ nullptr, options.forEval, - evalCaller, insideNonGlobalEval, - options.lineno, emitterMode); + evalCaller, insideNonGlobalEval, options.lineno, emitterMode); if (!bce.init()) return nullptr; // Syntax parsing may cause us to restart processing of top level // statements in the script. Use Maybe<> so that the parse context can be // reset when this occurs. Maybe<ParseContext<FullParseHandler> > pc; @@ -556,17 +557,17 @@ CompileFunctionBody(JSContext* cx, Mutab SourceCompressionTask sct(cx); MOZ_ASSERT(!options.sourceIsLazy); if (!cx->compartment()->options().discardSource()) { if (!ss->setSourceCopy(cx, srcBuf, true, &sct)) return false; } - bool canLazilyParse = CanLazilyParse(cx, options); + bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.emplace(cx, &cx->tempLifoAlloc(), options, srcBuf.get(), srcBuf.length(), /* foldConstants = */ false, (Parser<SyntaxParseHandler>*) nullptr, (LazyScript*) nullptr);
--- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -12,28 +12,27 @@ class JSLinearString; namespace js { class AutoNameVector; class LazyScript; class LifoAlloc; class ScriptSourceObject; -class StaticEvalObject; +class ScopeObject; struct SourceCompressionTask; namespace frontend { JSScript* CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, - HandleObject scopeChain, HandleScript evalCaller, - Handle<StaticEvalObject*> evalStaticScope, - const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, - JSString* source_ = nullptr, unsigned staticLevel = 0, - SourceCompressionTask* extraSct = nullptr); + HandleObject scopeChain, Handle<ScopeObject*> enclosingStaticScope, + HandleScript evalCaller, const ReadOnlyCompileOptions& options, + SourceBufferHolder& srcBuf, JSString* source_ = nullptr, + unsigned staticLevel = 0, SourceCompressionTask* extraSct = nullptr); bool CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length); /* * enclosingStaticScope is a static enclosing scope (e.g. a StaticWithObject). * Must be null if the enclosing scope is a global. */
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -764,17 +764,17 @@ BytecodeEmitter::enclosingStaticScope() if (staticScope) return staticScope; if (!sc->isFunctionBox()) { MOZ_ASSERT(!parent); // Top-level eval scripts have a placeholder static scope so that // StaticScopeIter may iterate through evals. - return sc->asGlobalSharedContext()->evalStaticScope(); + return sc->asGlobalSharedContext()->topStaticScope(); } return sc->asFunctionBox()->function(); } #ifdef DEBUG static bool AllLocalsAliased(StaticBlockObject& obj) @@ -1543,24 +1543,24 @@ BytecodeEmitter::tryConvertFreeName(Pars return false; RootedObject outerScope(cx, script->enclosingStaticScope()); for (StaticScopeIter<CanGC> ssi(cx, outerScope); !ssi.done(); ssi++) { if (ssi.type() != StaticScopeIter<CanGC>::Function) { if (ssi.type() == StaticScopeIter<CanGC>::Block) { // Use generic ops if a catch block is encountered. return false; } - if (ssi.hasDynamicScopeObject()) + if (ssi.hasSyntacticDynamicScopeObject()) hops++; continue; } RootedScript script(cx, ssi.funScript()); if (script->functionNonDelazifying()->atom() == pn->pn_atom) return false; - if (ssi.hasDynamicScopeObject()) { + if (ssi.hasSyntacticDynamicScopeObject()) { uint32_t slot; if (lookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot, pn)) { JSOp op; switch (pn->getOp()) { case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break; case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break; default: return false; } @@ -1582,19 +1582,19 @@ BytecodeEmitter::tryConvertFreeName(Pars } } // Unbound names aren't recognizable global-property references if the // script is inside a non-global eval call. if (insideNonGlobalEval) return false; - // Skip trying to use GNAME ops if we know our script has a polluted - // global scope, since they'll just get treated as NAME ops anyway. - if (script->hasPollutedGlobalScope()) + // Skip trying to use GNAME ops if we know our script has a non-syntactic + // scope, since they'll just get treated as NAME ops anyway. + if (script->hasNonSyntacticScope()) return false; // Deoptimized names also aren't necessarily globals. if (pn->isDeoptimized()) return false; if (sc->isFunctionBox()) { // Unbound names in function code may not be globals if new locals can @@ -2351,23 +2351,24 @@ bool BytecodeEmitter::checkRunOnceContext() { return checkSingletonContext() || (!isInLoop() && isRunOnceLambda()); } bool BytecodeEmitter::needsImplicitThis() { - if (sc->isFunctionBox() && sc->asFunctionBox()->inWith) + if (sc->inWith()) return true; for (StmtInfoBCE* stmt = topStmt; stmt; stmt = stmt->down) { if (stmt->type == STMT_WITH) return true; } + return false; } void BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx) { // Note: when parsing off thread the resulting scripts need to be handed to // the debugger after rejoining to the main thread. @@ -3401,16 +3402,29 @@ BytecodeEmitter::emitFunctionScript(Pars /* * IonBuilder has assumptions about what may occur immediately after * script->main (e.g., in the case of destructuring params). Thus, put the * following ops into the range [script->code, script->main). Note: * execution starts from script->code, so this has no semantic effect. */ FunctionBox* funbox = sc->asFunctionBox(); + + // Link the function and the script to each other, so that StaticScopeIter + // may walk the scope chain of currently compiling scripts. + RootedFunction fun(cx, funbox->function()); + MOZ_ASSERT(fun->isInterpreted()); + + script->setFunction(fun); + + if (fun->isInterpretedLazy()) + fun->setUnlazifiedScript(script); + else + fun->setScript(script); + if (funbox->argumentsHasLocalBinding()) { MOZ_ASSERT(offset() == 0); /* See JSScript::argumentsBytecode. */ switchToPrologue(); if (!emit1(JSOP_ARGUMENTS)) return false; InternalBindingsHandle bindings(script, &script->bindings); BindingIter bi = Bindings::argumentsBinding(cx, bindings); if (script->bindingIsAliased(bi)) { @@ -3504,25 +3518,16 @@ BytecodeEmitter::emitFunctionScript(Pars * If this function is only expected to run once, mark the script so that * initializers created within it may be given more precise types. */ if (runOnce) { script->setTreatAsRunOnce(); MOZ_ASSERT(!script->hasRunOnce()); } - /* Initialize fun->script() so that the debugger has a valid fun->script(). */ - RootedFunction fun(cx, script->functionNonDelazifying()); - MOZ_ASSERT(fun->isInterpreted()); - - if (fun->isInterpretedLazy()) - fun->setUnlazifiedScript(script); - else - fun->setScript(script); - tellDebuggerAboutCompiledScript(cx); return true; } bool BytecodeEmitter::maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result) { @@ -5760,18 +5765,16 @@ BytecodeEmitter::emitFunction(ParseNode* if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals()) funbox->setMightAliasLocals(); // inherit mightAliasLocals from parent MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); // Inherit most things (principals, version, etc) from the parent. Rooted<JSScript*> parent(cx, script); CompileOptions options(cx, parser->options()); options.setMutedErrors(parent->mutedErrors()) - .setHasPollutedScope(parent->hasPollutedGlobalScope()) - .setSelfHostingMode(parent->selfHosted()) .setNoScriptRval(false) .setForEval(false) .setVersion(parent->getVersion()); Rooted<JSObject*> enclosingScope(cx, enclosingStaticScope()); Rooted<JSObject*> sourceObject(cx, script->sourceObject()); Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options, parent->staticLevel() + 1,
--- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -17,17 +17,17 @@ #include "frontend/ParseMaps.h" #include "frontend/Parser.h" #include "frontend/SharedContext.h" #include "frontend/SourceNotes.h" namespace js { -class StaticEvalObject; +class ScopeObject; namespace frontend { class FullParseHandler; class ObjectBox; class ParseNode; template <typename ParseHandler> class Parser; class SharedContext;
--- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -1082,16 +1082,17 @@ Fold(ExclusiveContext* cx, ParseNode** p if (name && NameToId(name) == IdToTypeId(NameToId(name))) { // Optimization 3: We have pn1["foo"] where foo is not an index. // Convert to a property access (like pn1.foo) which we optimize // better downstream. Don't bother with this for names which TI // considers to be indexes, to simplify downstream analysis. ParseNode* expr = handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end); if (!expr) return false; + expr->setInParens(pn->isInParens()); ReplaceNode(pnp, expr); // Supposing we're replacing |obj["prop"]| with |obj.prop|, we now // can free the |"prop"| node and |obj["prop"]| nodes -- but not // the |obj| node now a sub-node of |expr|. Mutate |pn| into a // node with |"prop"| as its child so that our |pn| doesn't have a // necessarily-weird structure (say, by nulling out |pn->pn_left| // only) that would fail AST sanity assertions performed by
--- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -78,18 +78,31 @@ class FullParseHandler return node->isKind(PNK_SUPERPROP) || node->isKind(PNK_SUPERELEM); } bool isFunctionCall(ParseNode* node) { // Note: super() is a special form, *not* a function call. return node->isKind(PNK_CALL); } - bool isDestructuringTarget(ParseNode* node) { - return node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY); + static bool isUnparenthesizedDestructuringPattern(ParseNode* node) { + return !node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY)); + } + + static bool isParenthesizedDestructuringPattern(ParseNode* node) { + // Technically this isn't a destructuring pattern at all -- the grammar + // doesn't treat it as such. But we need to know when this happens to + // consider it a SyntaxError rather than an invalid-left-hand-side + // ReferenceError. + return node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY)); + } + + static bool isDestructuringPatternAnyParentheses(ParseNode* node) { + return isUnparenthesizedDestructuringPattern(node) || + isParenthesizedDestructuringPattern(node); } FullParseHandler(ExclusiveContext* cx, LifoAlloc& alloc, TokenStream& tokenStream, Parser<SyntaxParseHandler>* syntaxParser, LazyScript* lazyOuterFunction) : allocator(cx, alloc), tokenStream(tokenStream), lazyOuterFunction_(lazyOuterFunction), @@ -775,19 +788,35 @@ class FullParseHandler } void setPrologue(ParseNode* pn) { pn->pn_prologue = true; } bool isConstant(ParseNode* pn) { return pn->isConstant(); } - PropertyName* maybeName(ParseNode* pn) { - return pn->isKind(PNK_NAME) ? pn->pn_atom->asPropertyName() : nullptr; + + PropertyName* maybeUnparenthesizedName(ParseNode* pn) { + if (!pn->isInParens() && pn->isKind(PNK_NAME)) + return pn->pn_atom->asPropertyName(); + return nullptr; } + + PropertyName* maybeParenthesizedName(ParseNode* pn) { + if (pn->isInParens() && pn->isKind(PNK_NAME)) + return pn->pn_atom->asPropertyName(); + return nullptr; + } + + PropertyName* maybeNameAnyParentheses(ParseNode* node) { + if (PropertyName* name = maybeUnparenthesizedName(node)) + return name; + return maybeParenthesizedName(node); + } + bool isCall(ParseNode* pn) { return pn->isKind(PNK_CALL); } PropertyName* maybeDottedProperty(ParseNode* pn) { return pn->is<PropertyAccess>() ? &pn->as<PropertyAccess>().name() : nullptr; } JSAtom* isStringExprStatement(ParseNode* pn, TokenPos* pos) { if (JSAtom* atom = pn->isStringExprStatement()) {
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -591,54 +591,63 @@ FunctionBox::FunctionBox(ExclusiveContex bool extraWarnings, GeneratorKind generatorKind) : ObjectBox(fun, traceListHead), SharedContext(cx, directives, extraWarnings), bindings(), bufStart(0), bufEnd(0), length(0), generatorKindBits_(GeneratorKindAsBits(generatorKind)), - inWith(false), // initialized below + inWith_(false), // initialized below inGenexpLambda(false), hasDestructuringArgs(false), useAsm(false), insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()), usesArguments(false), usesApply(false), usesThis(false), funCxFlags() { // Functions created at parse time may be set singleton after parsing and // baked into JIT code, so they must be allocated tenured. They are held by // the JSScript so cannot be collected during a minor GC anyway. MOZ_ASSERT(fun->isTenured()); if (!outerpc) { - inWith = false; + inWith_ = false; } else if (outerpc->parsingWith) { // This covers cases that don't involve eval(). For example: // // with (o) { (function() { g(); })(); } // // In this case, |outerpc| corresponds to global code, and // outerpc->parsingWith is true. - inWith = true; + inWith_ = true; } else if (outerpc->sc->isFunctionBox()) { // This is like the above case, but for more deeply nested functions. // For example: // // with (o) { eval("(function() { (function() { g(); })(); })();"); } } // // In this case, the inner anonymous function needs to inherit the // setting of |inWith| from the outer one. FunctionBox* parent = outerpc->sc->asFunctionBox(); - if (parent && parent->inWith) - inWith = true; + if (parent && parent->inWith()) + inWith_ = true; + } else { + // This is like the above case, but when inside eval. + // + // For example: + // + // with(o) { eval("(function() { g(); })();"); } + // + // In this case, the static scope chain tells us the presence of with. + inWith_ = outerpc->sc->inWith(); } } template <typename ParseHandler> FunctionBox* Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, ParseContext<ParseHandler>* outerpc, Directives inheritedDirectives, GeneratorKind generatorKind) { @@ -1150,17 +1159,17 @@ Parser<FullParseHandler>::makeDefIntoUse return true; } /* * Parameter block types for the several Binder functions. We use a common * helper function signature in order to share code among destructuring and * simple variable declaration parsers. In the destructuring case, the binder * function is called indirectly from the variable declaration parser by way - * of checkDestructuring and its friends. + * of checkDestructuringPattern and its friends. */ template <typename ParseHandler> struct BindData