From b5f50ed83444015c3ef69331f780a3258e21fd3d Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Tue, 4 Jun 2019 11:59:33 -0700 Subject: [PATCH 1/8] Bug 1556817 - U+FFFE should not be treated as white space. r=arai Differential Revision: https://phabricator.services.mozilla.com/D34469 --HG-- extra : rebase_source : 827a20fa744a5ca2c7cfd3572937a8f548ca958f extra : amend_source : 0d182034da1f15df49dc42cb720700e5f6438a8a extra : source : f877ed444562df03ef9ee92da3099e2f0e4e7f07 --- js/src/frontend/TokenStream.cpp | 21 +++++---- .../tests/non262/extensions/regress-368516.js | 3 +- js/src/util/Unicode.h | 44 ++++++++++++------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index a658559a3db3..aaf7863c2546 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1992,7 +1992,7 @@ MOZ_MUST_USE bool TokenStreamSpecific::getDirective( } if (MOZ_LIKELY(isAsciiCodePoint(unit))) { - if (unicode::IsSpaceOrBOM2(unit)) { + if (unicode::IsSpace(AssertedCast(unit))) { break; } @@ -2016,13 +2016,13 @@ MOZ_MUST_USE bool TokenStreamSpecific::getDirective( // This ignores encoding errors: subsequent caller-side code to // handle the remaining source text in the comment will do so. PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); - if (peeked.isNone() || unicode::IsSpaceOrBOM2(peeked.codePoint())) { + if (peeked.isNone() || unicode::IsSpace(peeked.codePoint())) { break; } MOZ_ASSERT(!IsLineTerminator(peeked.codePoint()), - "!IsSpaceOrBOM2 must imply !IsLineTerminator or else we'll " - "fail to maintain line-info/flags for EOL"); + "!IsSpace must imply !IsLineTerminator or else we'll fail to " + "maintain line-info/flags for EOL"); this->sourceUnits.consumeKnownCodePoint(peeked); if (!appendCodePointToCharBuffer(peeked.codePoint())) { @@ -2743,12 +2743,11 @@ MOZ_MUST_USE bool TokenStreamSpecific::getTokenInternal( } if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { - // Non-ASCII code points can only be identifiers or whitespace. - // It would be nice to compute these *after* discarding whitespace, - // but IN A WORLD where |unicode::IsSpaceOrBOM2| requires consuming - // a variable number of code points, it's easier to assume it's an - // identifier and maybe do a little wasted work, than to unget and - // compute and reget if whitespace. + // Non-ASCII code points can only be identifiers or whitespace. It would + // be nice to compute these *after* discarding whitespace, but IN A WORLD + // where |unicode::IsSpace| requires consuming a variable number of code + // units, it's easier to assume it's an identifier and maybe do a little + // wasted work, than to unget and compute and reget if whitespace. TokenStart start(this->sourceUnits, 0); const Unit* identStart = this->sourceUnits.addressOfNextCodeUnit(); @@ -2760,7 +2759,7 @@ MOZ_MUST_USE bool TokenStreamSpecific::getTokenInternal( } char32_t cp = peeked.codePoint(); - if (unicode::IsSpaceOrBOM2(cp)) { + if (unicode::IsSpace(cp)) { this->sourceUnits.consumeKnownCodePoint(peeked); if (IsLineTerminator(cp)) { if (!updateLineInfoForEOL()) { diff --git a/js/src/tests/non262/extensions/regress-368516.js b/js/src/tests/non262/extensions/regress-368516.js index c3e109f7cd26..667b5c1535e1 100644 --- a/js/src/tests/non262/extensions/regress-368516.js +++ b/js/src/tests/non262/extensions/regress-368516.js @@ -19,8 +19,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - var bomchars = ['\uFFFE', - '\uFEFF']; + var bomchars = ['\uFEFF']; for (var i = 0; i < bomchars.length; i++) { diff --git a/js/src/util/Unicode.h b/js/src/util/Unicode.h index 41f79821fcc4..e67d4d723941 100644 --- a/js/src/util/Unicode.h +++ b/js/src/util/Unicode.h @@ -80,7 +80,6 @@ constexpr char16_t GREEK_SMALL_LETTER_SIGMA = 0x03C3; constexpr char16_t LINE_SEPARATOR = 0x2028; constexpr char16_t PARA_SEPARATOR = 0x2029; constexpr char16_t REPLACEMENT_CHARACTER = 0xFFFD; -constexpr char16_t BYTE_ORDER_MARK2 = 0xFFFE; const char16_t LeadSurrogateMin = 0xD800; const char16_t LeadSurrogateMax = 0xDBFF; @@ -204,19 +203,28 @@ inline bool IsUnicodeIDStart(uint32_t codePoint) { return IsUnicodeIDStart(char16_t(codePoint)); } +// IsSpace checks if a code point is included in the merged set of WhiteSpace +// and LineTerminator specified by #sec-white-space and #sec-line-terminators. +// We combine them because nearly every calling function wants this, excepting +// only some tokenizer code that necessarily handles LineTerminator specially +// due to UTF-8/UTF-16 template specialization. inline bool IsSpace(char16_t ch) { - /* - * IsSpace checks if some character is included in the merged set - * of WhiteSpace and LineTerminator, specified by ES2016 11.2 and 11.3. - * We combined them, because in practice nearly every - * calling function wants this, except some code in the tokenizer. - * - * We use a lookup table for ASCII-7 characters, because they are - * very common and must be handled quickly in the tokenizer. - * NO-BREAK SPACE is supposed to be the most common character not in - * this range, so we inline this case, too. - */ + // ASCII code points are very common and must be handled quickly, so use a + // lookup table for them. + if (ch < 128) { + return js_isspace[ch]; + } + // NO-BREAK SPACE is supposed to be the most common non-ASCII WhiteSpace code + // point, so inline its handling too. + if (ch == NO_BREAK_SPACE) { + return true; + } + + return CharInfo(ch).isSpace(); +} + +inline bool IsSpace(JS::Latin1Char ch) { if (ch < 128) { return js_isspace[ch]; } @@ -225,16 +233,20 @@ inline bool IsSpace(char16_t ch) { return true; } - return CharInfo(ch).isSpace(); + MOZ_ASSERT(!CharInfo(ch).isSpace()); + return false; } -inline bool IsSpaceOrBOM2(char32_t ch) { +inline bool IsSpace(char ch) { + return IsSpace(static_cast(ch)); +} + +inline bool IsSpace(char32_t ch) { if (ch < 128) { return js_isspace[ch]; } - /* We accept BOM2 (0xFFFE) for compatibility reasons in the parser. */ - if (ch == NO_BREAK_SPACE || ch == BYTE_ORDER_MARK2) { + if (ch == NO_BREAK_SPACE) { return true; } From a7f81b45b53129fce7fb2fb93d732aa990d76f15 Mon Sep 17 00:00:00 2001 From: Jan Varga Date: Thu, 13 Jun 2019 04:17:52 +0200 Subject: [PATCH 2/8] Bug 1559029 - LSNG: Connection::FlushOp::DoDatastoreWork needs to automatically rollback the transaction and detach the shadow database on an error; r=asuth Differential Revision: https://phabricator.services.mozilla.com/D34814 --- dom/localstorage/ActorsParent.cpp | 296 ++++++++++++++------ dom/localstorage/test/unit/head.js | 12 + dom/localstorage/test/unit/test_flushing.js | 67 +++++ dom/localstorage/test/unit/xpcshell.ini | 1 + 4 files changed, 295 insertions(+), 81 deletions(-) create mode 100644 dom/localstorage/test/unit/test_flushing.js diff --git a/dom/localstorage/ActorsParent.cpp b/dom/localstorage/ActorsParent.cpp index bcce87ba22ee..f0374271a1a2 100644 --- a/dom/localstorage/ActorsParent.cpp +++ b/dom/localstorage/ActorsParent.cpp @@ -1433,6 +1433,12 @@ class Connection final { void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); } + QuotaClient* GetQuotaClient() const { + MOZ_ASSERT(mQuotaClient); + + return mQuotaClient; + } + ArchivedOriginScope* GetArchivedOriginScope() const { return mArchivedOriginScope; } @@ -1479,6 +1485,12 @@ class Connection final { nsresult GetCachedStatement(const nsACString& aQuery, CachedStatement* aCachedStatement); + nsresult BeginWriteTransaction(); + + nsresult CommitWriteTransaction(); + + nsresult RollbackWriteTransaction(); + private: // Only created by ConnectionThread. Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix, @@ -2819,6 +2831,30 @@ class QuotaClient::MatchFunction final : public mozIStorageFunction { NS_DECL_MOZISTORAGEFUNCTION }; +/******************************************************************************* + * Helper classes + ******************************************************************************/ + +class MOZ_STACK_CLASS AutoWriteTransaction final { + Connection* mConnection; + Maybe mShadowDatabaseLock; + bool mShadowWrites; + + public: + explicit AutoWriteTransaction(bool aShadowWrites); + + ~AutoWriteTransaction(); + + nsresult Start(Connection* aConnection); + + nsresult Commit(); + + private: + nsresult LockAndAttachShadowDatabase(Connection* aConnection); + + nsresult DetachShadowDatabaseAndUnlock(); +}; + /******************************************************************************* * Globals ******************************************************************************/ @@ -4342,6 +4378,60 @@ nsresult Connection::GetCachedStatement(const nsACString& aQuery, return NS_OK; } +nsresult Connection::BeginWriteTransaction() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mStorageConnection); + + CachedStatement stmt; + nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), + &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult Connection::CommitWriteTransaction() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mStorageConnection); + + CachedStatement stmt; + nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult Connection::RollbackWriteTransaction() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mStorageConnection); + + CachedStatement stmt; + nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // This may fail if SQLite already rolled back the transaction so ignore any + // errors. + Unused << stmt->Execute(); + + return NS_OK; +} + void Connection::ScheduleFlush() { AssertIsOnOwningThread(); MOZ_ASSERT(mWriteOptimizer.HasWrites()); @@ -4497,69 +4587,9 @@ nsresult Connection::FlushOp::DoDatastoreWork() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); - class MOZ_STACK_CLASS AutoDetach final { - nsCOMPtr mConnection; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + AutoWriteTransaction autoWriteTransaction(mShadowWrites); - public: - explicit AutoDetach( - mozIStorageConnection* aConnection MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : mConnection(aConnection) { - MOZ_ASSERT(aConnection); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } - - ~AutoDetach() { - if (mConnection) { - nsresult rv = DetachShadowDatabase(mConnection); - Unused << NS_WARN_IF(NS_FAILED(rv)); - } - } - - void release() { mConnection = nullptr; } - - private: - explicit AutoDetach(const AutoDetach&) = delete; - AutoDetach& operator=(const AutoDetach&) = delete; - AutoDetach& operator=(AutoDetach&&) = delete; - }; - - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - - nsCOMPtr storageConnection = - mConnection->StorageConnection(); - MOZ_ASSERT(storageConnection); - - nsresult rv; - - Maybe shadowDatabaseLock; - - Maybe autoDetach; - - if (mShadowWrites) { - MOZ_ASSERT(mConnection->mQuotaClient); - - shadowDatabaseLock.emplace( - mConnection->mQuotaClient->ShadowDatabaseMutex()); - - rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - autoDetach.emplace(storageConnection); - } - - CachedStatement stmt; - rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), - &stmt); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - rv = stmt->Execute(); + nsresult rv = autoWriteTransaction.Start(mConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -4588,34 +4618,19 @@ nsresult Connection::FlushOp::DoDatastoreWork() { return rv; } - rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); + rv = autoWriteTransaction.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = stmt->Execute(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - if (mShadowWrites) { - autoDetach->release(); - - rv = DetachShadowDatabase(storageConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - autoDetach.reset(); - - shadowDatabaseLock.reset(); - } - rv = usageJournalFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + RefPtr runnable = NS_NewRunnableFunction("dom::localstorage::UpdateUsageRunnable", [origin = mConnection->Origin(), usage]() { @@ -9351,5 +9366,124 @@ QuotaClient::MatchFunction::OnFunctionCall( return NS_OK; } +/******************************************************************************* + * AutoWriteTransaction + ******************************************************************************/ + +AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites) + : mConnection(nullptr) + , mShadowWrites(aShadowWrites) +{ + AssertIsOnConnectionThread(); + + MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction); +} + +AutoWriteTransaction::~AutoWriteTransaction() { + AssertIsOnConnectionThread(); + + MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction); + + if (mConnection) { + if (NS_FAILED(mConnection->RollbackWriteTransaction())) { + NS_WARNING("Failed to rollback write transaction!"); + } + + if (mShadowWrites && NS_FAILED(DetachShadowDatabaseAndUnlock())) { + NS_WARNING("Failed to detach shadow database!"); + } + } +} + +nsresult AutoWriteTransaction::Start(Connection* aConnection) { + AssertIsOnConnectionThread(); + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!mConnection); + + nsresult rv; + + if (mShadowWrites) { + rv = LockAndAttachShadowDatabase(aConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = aConnection->BeginWriteTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mConnection = aConnection; + + return NS_OK; +} + +nsresult AutoWriteTransaction::Commit() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mConnection); + + nsresult rv = mConnection->CommitWriteTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mShadowWrites) { + rv = DetachShadowDatabaseAndUnlock(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mConnection = nullptr; + + return NS_OK; +} + +nsresult AutoWriteTransaction::LockAndAttachShadowDatabase(Connection* aConnection) { + AssertIsOnConnectionThread(); + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!mConnection); + MOZ_ASSERT(mShadowDatabaseLock.isNothing()); + MOZ_ASSERT(mShadowWrites); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsCOMPtr storageConnection = + aConnection->StorageConnection(); + MOZ_ASSERT(storageConnection); + + mShadowDatabaseLock.emplace( + aConnection->GetQuotaClient()->ShadowDatabaseMutex()); + + nsresult rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mConnection); + MOZ_ASSERT(mShadowDatabaseLock.isSome()); + MOZ_ASSERT(mShadowWrites); + + nsCOMPtr storageConnection = + mConnection->StorageConnection(); + MOZ_ASSERT(storageConnection); + + nsresult rv = DetachShadowDatabase(storageConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mShadowDatabaseLock.reset(); + + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/localstorage/test/unit/head.js b/dom/localstorage/test/unit/head.js index a1dcfc65d7cb..8dabf603f285 100644 --- a/dom/localstorage/test/unit/head.js +++ b/dom/localstorage/test/unit/head.js @@ -85,6 +85,18 @@ function resetOriginLimit() { Services.prefs.clearUserPref("dom.storage.default_quota"); } +function setTimeout(callback, timeout) { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + timer.initWithCallback({ + notify(timer) { + callback(); + }, + }, timeout, Ci.nsITimer.TYPE_ONE_SHOT); + + return timer; +} + function init() { let request = Services.qms.init(); diff --git a/dom/localstorage/test/unit/test_flushing.js b/dom/localstorage/test/unit/test_flushing.js new file mode 100644 index 000000000000..101a507e2961 --- /dev/null +++ b/dom/localstorage/test/unit/test_flushing.js @@ -0,0 +1,67 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +/** + * This test is mainly to verify that the flush operation detaches the shadow + * database in the event of early return due to error. See bug 1559029. + */ + +async function testSteps() { + const principal1 = getPrincipal("http://example1.com"); + + const usageFile1 = + getRelativeFile("storage/default/http+++example1.com/ls/usage"); + + const principal2 = getPrincipal("http://example2.com"); + + const data = { + key: "foo", + value: "bar", + }; + + const flushSleepTimeSec = 6; + + info("Setting prefs"); + + Services.prefs.setBoolPref("dom.storage.next_gen", true); + + info("Getting storage 1"); + + let storage1 = getLocalStorage(principal1); + + info("Adding item"); + + storage1.setItem(data.key, data.value); + + info("Creating usage as a directory"); + + // This will cause a failure during the flush for first principal. + usageFile1.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); + + info("Getting storage 2"); + + let storage2 = getLocalStorage(principal2); + + info("Adding item"); + + storage2.setItem(data.key, data.value); + + // The flush for second principal shouldn't be affected by failed flush for + // first principal. + + info("Sleeping for " + flushSleepTimeSec + " seconds to let all flushes " + + "finish"); + + await new Promise(function(resolve) { + setTimeout(resolve, flushSleepTimeSec * 1000); + }); + + info("Resetting"); + + // Wait for all database connections to close. + let request = reset(); + await requestFinished(request); +} diff --git a/dom/localstorage/test/unit/xpcshell.ini b/dom/localstorage/test/unit/xpcshell.ini index c490897c8e41..194d71d9ebe9 100644 --- a/dom/localstorage/test/unit/xpcshell.ini +++ b/dom/localstorage/test/unit/xpcshell.ini @@ -33,6 +33,7 @@ run-sequentially = test_databaseShadowing_clearOriginsByPrefix2.js depends on a [test_databaseShadowing_clearOriginsByPrefix2.js] run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js [test_eviction.js] +[test_flushing.js] [test_groupLimit.js] [test_groupMismatch.js] [test_largeItems.js] From 94dd7926006bf6e1660c5f007781120530e00de1 Mon Sep 17 00:00:00 2001 From: Razvan Maries Date: Thu, 13 Jun 2019 07:33:58 +0300 Subject: [PATCH 3/8] Backed out changeset 6baa1883dc72 (bug 1559029) for ES Lint failure. --- dom/localstorage/ActorsParent.cpp | 296 ++++++-------------- dom/localstorage/test/unit/head.js | 12 - dom/localstorage/test/unit/test_flushing.js | 67 ----- dom/localstorage/test/unit/xpcshell.ini | 1 - 4 files changed, 81 insertions(+), 295 deletions(-) delete mode 100644 dom/localstorage/test/unit/test_flushing.js diff --git a/dom/localstorage/ActorsParent.cpp b/dom/localstorage/ActorsParent.cpp index f0374271a1a2..bcce87ba22ee 100644 --- a/dom/localstorage/ActorsParent.cpp +++ b/dom/localstorage/ActorsParent.cpp @@ -1433,12 +1433,6 @@ class Connection final { void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); } - QuotaClient* GetQuotaClient() const { - MOZ_ASSERT(mQuotaClient); - - return mQuotaClient; - } - ArchivedOriginScope* GetArchivedOriginScope() const { return mArchivedOriginScope; } @@ -1485,12 +1479,6 @@ class Connection final { nsresult GetCachedStatement(const nsACString& aQuery, CachedStatement* aCachedStatement); - nsresult BeginWriteTransaction(); - - nsresult CommitWriteTransaction(); - - nsresult RollbackWriteTransaction(); - private: // Only created by ConnectionThread. Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix, @@ -2831,30 +2819,6 @@ class QuotaClient::MatchFunction final : public mozIStorageFunction { NS_DECL_MOZISTORAGEFUNCTION }; -/******************************************************************************* - * Helper classes - ******************************************************************************/ - -class MOZ_STACK_CLASS AutoWriteTransaction final { - Connection* mConnection; - Maybe mShadowDatabaseLock; - bool mShadowWrites; - - public: - explicit AutoWriteTransaction(bool aShadowWrites); - - ~AutoWriteTransaction(); - - nsresult Start(Connection* aConnection); - - nsresult Commit(); - - private: - nsresult LockAndAttachShadowDatabase(Connection* aConnection); - - nsresult DetachShadowDatabaseAndUnlock(); -}; - /******************************************************************************* * Globals ******************************************************************************/ @@ -4378,60 +4342,6 @@ nsresult Connection::GetCachedStatement(const nsACString& aQuery, return NS_OK; } -nsresult Connection::BeginWriteTransaction() { - AssertIsOnConnectionThread(); - MOZ_ASSERT(mStorageConnection); - - CachedStatement stmt; - nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), - &stmt); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - rv = stmt->Execute(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - return NS_OK; -} - -nsresult Connection::CommitWriteTransaction() { - AssertIsOnConnectionThread(); - MOZ_ASSERT(mStorageConnection); - - CachedStatement stmt; - nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - rv = stmt->Execute(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - return NS_OK; -} - -nsresult Connection::RollbackWriteTransaction() { - AssertIsOnConnectionThread(); - MOZ_ASSERT(mStorageConnection); - - CachedStatement stmt; - nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &stmt); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - // This may fail if SQLite already rolled back the transaction so ignore any - // errors. - Unused << stmt->Execute(); - - return NS_OK; -} - void Connection::ScheduleFlush() { AssertIsOnOwningThread(); MOZ_ASSERT(mWriteOptimizer.HasWrites()); @@ -4587,9 +4497,69 @@ nsresult Connection::FlushOp::DoDatastoreWork() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); - AutoWriteTransaction autoWriteTransaction(mShadowWrites); + class MOZ_STACK_CLASS AutoDetach final { + nsCOMPtr mConnection; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER - nsresult rv = autoWriteTransaction.Start(mConnection); + public: + explicit AutoDetach( + mozIStorageConnection* aConnection MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mConnection(aConnection) { + MOZ_ASSERT(aConnection); + + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + + ~AutoDetach() { + if (mConnection) { + nsresult rv = DetachShadowDatabase(mConnection); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + } + + void release() { mConnection = nullptr; } + + private: + explicit AutoDetach(const AutoDetach&) = delete; + AutoDetach& operator=(const AutoDetach&) = delete; + AutoDetach& operator=(AutoDetach&&) = delete; + }; + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsCOMPtr storageConnection = + mConnection->StorageConnection(); + MOZ_ASSERT(storageConnection); + + nsresult rv; + + Maybe shadowDatabaseLock; + + Maybe autoDetach; + + if (mShadowWrites) { + MOZ_ASSERT(mConnection->mQuotaClient); + + shadowDatabaseLock.emplace( + mConnection->mQuotaClient->ShadowDatabaseMutex()); + + rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + autoDetach.emplace(storageConnection); + } + + CachedStatement stmt; + rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), + &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -4618,19 +4588,34 @@ nsresult Connection::FlushOp::DoDatastoreWork() { return rv; } - rv = autoWriteTransaction.Commit(); + rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mShadowWrites) { + autoDetach->release(); + + rv = DetachShadowDatabase(storageConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + autoDetach.reset(); + + shadowDatabaseLock.reset(); + } + rv = usageJournalFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - RefPtr runnable = NS_NewRunnableFunction("dom::localstorage::UpdateUsageRunnable", [origin = mConnection->Origin(), usage]() { @@ -9366,124 +9351,5 @@ QuotaClient::MatchFunction::OnFunctionCall( return NS_OK; } -/******************************************************************************* - * AutoWriteTransaction - ******************************************************************************/ - -AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites) - : mConnection(nullptr) - , mShadowWrites(aShadowWrites) -{ - AssertIsOnConnectionThread(); - - MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction); -} - -AutoWriteTransaction::~AutoWriteTransaction() { - AssertIsOnConnectionThread(); - - MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction); - - if (mConnection) { - if (NS_FAILED(mConnection->RollbackWriteTransaction())) { - NS_WARNING("Failed to rollback write transaction!"); - } - - if (mShadowWrites && NS_FAILED(DetachShadowDatabaseAndUnlock())) { - NS_WARNING("Failed to detach shadow database!"); - } - } -} - -nsresult AutoWriteTransaction::Start(Connection* aConnection) { - AssertIsOnConnectionThread(); - MOZ_ASSERT(aConnection); - MOZ_ASSERT(!mConnection); - - nsresult rv; - - if (mShadowWrites) { - rv = LockAndAttachShadowDatabase(aConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - } - - rv = aConnection->BeginWriteTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - mConnection = aConnection; - - return NS_OK; -} - -nsresult AutoWriteTransaction::Commit() { - AssertIsOnConnectionThread(); - MOZ_ASSERT(mConnection); - - nsresult rv = mConnection->CommitWriteTransaction(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - if (mShadowWrites) { - rv = DetachShadowDatabaseAndUnlock(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - } - - mConnection = nullptr; - - return NS_OK; -} - -nsresult AutoWriteTransaction::LockAndAttachShadowDatabase(Connection* aConnection) { - AssertIsOnConnectionThread(); - MOZ_ASSERT(aConnection); - MOZ_ASSERT(!mConnection); - MOZ_ASSERT(mShadowDatabaseLock.isNothing()); - MOZ_ASSERT(mShadowWrites); - - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - - nsCOMPtr storageConnection = - aConnection->StorageConnection(); - MOZ_ASSERT(storageConnection); - - mShadowDatabaseLock.emplace( - aConnection->GetQuotaClient()->ShadowDatabaseMutex()); - - nsresult rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - return NS_OK; -} - -nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() { - AssertIsOnConnectionThread(); - MOZ_ASSERT(mConnection); - MOZ_ASSERT(mShadowDatabaseLock.isSome()); - MOZ_ASSERT(mShadowWrites); - - nsCOMPtr storageConnection = - mConnection->StorageConnection(); - MOZ_ASSERT(storageConnection); - - nsresult rv = DetachShadowDatabase(storageConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - mShadowDatabaseLock.reset(); - - return NS_OK; -} - } // namespace dom } // namespace mozilla diff --git a/dom/localstorage/test/unit/head.js b/dom/localstorage/test/unit/head.js index 8dabf603f285..a1dcfc65d7cb 100644 --- a/dom/localstorage/test/unit/head.js +++ b/dom/localstorage/test/unit/head.js @@ -85,18 +85,6 @@ function resetOriginLimit() { Services.prefs.clearUserPref("dom.storage.default_quota"); } -function setTimeout(callback, timeout) { - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - - timer.initWithCallback({ - notify(timer) { - callback(); - }, - }, timeout, Ci.nsITimer.TYPE_ONE_SHOT); - - return timer; -} - function init() { let request = Services.qms.init(); diff --git a/dom/localstorage/test/unit/test_flushing.js b/dom/localstorage/test/unit/test_flushing.js deleted file mode 100644 index 101a507e2961..000000000000 --- a/dom/localstorage/test/unit/test_flushing.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -/* eslint-disable mozilla/no-arbitrary-setTimeout */ - -/** - * This test is mainly to verify that the flush operation detaches the shadow - * database in the event of early return due to error. See bug 1559029. - */ - -async function testSteps() { - const principal1 = getPrincipal("http://example1.com"); - - const usageFile1 = - getRelativeFile("storage/default/http+++example1.com/ls/usage"); - - const principal2 = getPrincipal("http://example2.com"); - - const data = { - key: "foo", - value: "bar", - }; - - const flushSleepTimeSec = 6; - - info("Setting prefs"); - - Services.prefs.setBoolPref("dom.storage.next_gen", true); - - info("Getting storage 1"); - - let storage1 = getLocalStorage(principal1); - - info("Adding item"); - - storage1.setItem(data.key, data.value); - - info("Creating usage as a directory"); - - // This will cause a failure during the flush for first principal. - usageFile1.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); - - info("Getting storage 2"); - - let storage2 = getLocalStorage(principal2); - - info("Adding item"); - - storage2.setItem(data.key, data.value); - - // The flush for second principal shouldn't be affected by failed flush for - // first principal. - - info("Sleeping for " + flushSleepTimeSec + " seconds to let all flushes " + - "finish"); - - await new Promise(function(resolve) { - setTimeout(resolve, flushSleepTimeSec * 1000); - }); - - info("Resetting"); - - // Wait for all database connections to close. - let request = reset(); - await requestFinished(request); -} diff --git a/dom/localstorage/test/unit/xpcshell.ini b/dom/localstorage/test/unit/xpcshell.ini index 194d71d9ebe9..c490897c8e41 100644 --- a/dom/localstorage/test/unit/xpcshell.ini +++ b/dom/localstorage/test/unit/xpcshell.ini @@ -33,7 +33,6 @@ run-sequentially = test_databaseShadowing_clearOriginsByPrefix2.js depends on a [test_databaseShadowing_clearOriginsByPrefix2.js] run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js [test_eviction.js] -[test_flushing.js] [test_groupLimit.js] [test_groupMismatch.js] [test_largeItems.js] From 99aaa4e6c6d571fbe2706e2e4b7a451155bcff7e Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 11 Jun 2019 07:50:40 -0500 Subject: [PATCH 4/8] Bug 1558482 - Add visibleRect and scaling to EffectsInfo and compute them when using layers. r=mattwoodrow Differential Revision: https://phabricator.services.mozilla.com/D34523 --HG-- extra : rebase_source : 3bbef496359ae108e1f4adc2548e0140cfd7b0a8 --- dom/ipc/BrowserChild.cpp | 2 +- dom/ipc/EffectsInfo.h | 34 +++++++++++++++++---------- dom/ipc/TabMessageUtils.h | 8 +++++-- layout/generic/nsSubDocumentFrame.cpp | 21 ++++++++++++++--- layout/painting/FrameLayerBuilder.cpp | 10 ++++++++ layout/painting/FrameLayerBuilder.h | 7 ++++++ 6 files changed, 64 insertions(+), 18 deletions(-) diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 60915284a848..b6fa72a97e25 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -2741,7 +2741,7 @@ bool BrowserChild::IsVisible() { } void BrowserChild::UpdateVisibility(bool aForceRepaint) { - bool shouldBeVisible = mIsTopLevel ? mRenderLayers : mEffectsInfo.mVisible; + bool shouldBeVisible = mIsTopLevel ? mRenderLayers : mEffectsInfo.IsVisible(); bool isVisible = IsVisible(); if (shouldBeVisible != isVisible) { diff --git a/dom/ipc/EffectsInfo.h b/dom/ipc/EffectsInfo.h index 86da4a725145..e7bcbb11848e 100644 --- a/dom/ipc/EffectsInfo.h +++ b/dom/ipc/EffectsInfo.h @@ -7,6 +7,8 @@ #ifndef mozilla_dom_EffectsInfo_h #define mozilla_dom_EffectsInfo_h +#include "nsRect.h" + namespace mozilla { namespace dom { @@ -14,29 +16,37 @@ namespace dom { * An EffectsInfo contains information for a remote browser about the graphical * effects that are being applied to it by ancestor browsers in different * processes. - * - * TODO: This struct currently only reports visibility, and should be extended - * with information on clipping and scaling. */ class EffectsInfo { public: EffectsInfo() { *this = EffectsInfo::FullyHidden(); } - static EffectsInfo FullyVisible() { return EffectsInfo{true}; } - static EffectsInfo FullyHidden() { return EffectsInfo{false}; } + + static EffectsInfo VisibleWithinRect(const nsRect& aVisibleRect, + float aScaleX, float aScaleY) { + return EffectsInfo{aVisibleRect, aScaleX, aScaleY}; + } + static EffectsInfo FullyHidden() { return EffectsInfo{nsRect(), 1.0f, 1.0f}; } bool operator==(const EffectsInfo& aOther) { - return mVisible == aOther.mVisible; + return mVisibleRect == aOther.mVisibleRect && mScaleX == aOther.mScaleX && + mScaleY == aOther.mScaleY; } bool operator!=(const EffectsInfo& aOther) { return !(*this == aOther); } - // If you add new state here, you must also update operator== - bool mVisible; - /* - * TODO: Add information for ancestor scaling and clipping. - */ + bool IsVisible() const { return !mVisibleRect.IsEmpty(); } + + // The visible rect of this browser relative to the root frame. If this is + // empty then the browser can be considered invisible. + nsRect mVisibleRect; + // The desired scale factors to apply to rasterized content to match + // transforms applied in ancestor browsers. + float mScaleX; + float mScaleY; + // If you add new fields here, you must also update operator== private: - explicit EffectsInfo(bool aVisible) : mVisible(aVisible) {} + EffectsInfo(const nsRect& aVisibleRect, float aScaleX, float aScaleY) + : mVisibleRect(aVisibleRect), mScaleX(aScaleX), mScaleY(aScaleY) {} }; } // namespace dom diff --git a/dom/ipc/TabMessageUtils.h b/dom/ipc/TabMessageUtils.h index cb0944976bc0..f058c1ce13eb 100644 --- a/dom/ipc/TabMessageUtils.h +++ b/dom/ipc/TabMessageUtils.h @@ -81,12 +81,16 @@ struct ParamTraits { typedef mozilla::dom::EffectsInfo paramType; static void Write(Message* aMsg, const paramType& aParam) { - WriteParam(aMsg, aParam.mVisible); + WriteParam(aMsg, aParam.mVisibleRect); + WriteParam(aMsg, aParam.mScaleX); + WriteParam(aMsg, aParam.mScaleY); } static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) { - return ReadParam(aMsg, aIter, &aResult->mVisible); + return ReadParam(aMsg, aIter, &aResult->mVisibleRect) && + ReadParam(aMsg, aIter, &aResult->mScaleX) && + ReadParam(aMsg, aIter, &aResult->mScaleY); } }; diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp index 073ab3590c99..1797916be2a6 100644 --- a/layout/generic/nsSubDocumentFrame.cpp +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -14,6 +14,7 @@ #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs.h" +#include "mozilla/Unused.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/HTMLFrameElement.h" #include "mozilla/dom/BrowserParent.h" @@ -1404,8 +1405,20 @@ already_AddRefed nsDisplayRemote::BuildLayer( if (RefPtr remoteBrowser = GetFrameLoader()->GetRemoteBrowser()) { + // Adjust mItemVisibleRect, which is relative to the reference frame, to be + // relative to this frame + nsRect visibleRect; + if (aContainerParameters.mItemVisibleRect) { + visibleRect = *aContainerParameters.mItemVisibleRect - ToReferenceFrame(); + } else { + visibleRect = mFrame->GetContentRectRelativeToSelf(); + } + // Generate an effects update notifying the browser it is visible - aBuilder->AddEffectUpdate(remoteBrowser, EffectsInfo::FullyVisible()); + aBuilder->AddEffectUpdate(remoteBrowser, + EffectsInfo::VisibleWithinRect( + visibleRect, aContainerParameters.mXScale, + aContainerParameters.mYScale)); // FrameLayerBuilder will take care of notifying the browser when it is no // longer visible } @@ -1465,8 +1478,10 @@ bool nsDisplayRemote::CreateWebRenderCommands( if (RefPtr remoteBrowser = GetFrameLoader()->GetRemoteBrowser()) { // Generate an effects update notifying the browser it is visible - aDisplayListBuilder->AddEffectUpdate(remoteBrowser, - EffectsInfo::FullyVisible()); + // TODO - Gather visibleRect and scaling factors + aDisplayListBuilder->AddEffectUpdate( + remoteBrowser, EffectsInfo::VisibleWithinRect( + mFrame->GetContentRectRelativeToSelf(), 1.0f, 1.0f)); // Create a WebRenderRemoteData to notify the RemoteBrowser when it is no // longer visible diff --git a/layout/painting/FrameLayerBuilder.cpp b/layout/painting/FrameLayerBuilder.cpp index 56bd229ea040..4e2612f93201 100644 --- a/layout/painting/FrameLayerBuilder.cpp +++ b/layout/painting/FrameLayerBuilder.cpp @@ -4657,6 +4657,7 @@ void ContainerState::ProcessDisplayItems(nsDisplayList* aList) { transformNode = transformNode->Parent(); } + nsRect itemVisibleRectAu = itemContent; if (transformNode) { // If we are within transform, transform itemContent and itemDrawRect. MOZ_ASSERT(transformNode); @@ -4818,6 +4819,15 @@ void ContainerState::ProcessDisplayItems(nsDisplayList* aList) { ContainerLayerParameters params = mParameters; params.mBackgroundColor = uniformColor; params.mLayerCreationHint = GetLayerCreationHint(itemAGR); + if (!transformNode) { + params.mItemVisibleRect = &itemVisibleRectAu; + } else { + // We only use mItemVisibleRect for getting the visible rect for + // remote browsers (which should never have inactive transforms), so we + // avoid doing transforms on itemVisibleRectAu above and can't report + // an accurate bounds here. + params.mItemVisibleRect = nullptr; + } params.mScrollMetadataASR = ActiveScrolledRoot::IsAncestor(scrollMetadataASR, mContainerScrollMetadataASR) diff --git a/layout/painting/FrameLayerBuilder.h b/layout/painting/FrameLayerBuilder.h index 491c37a151b2..3a64053d426d 100644 --- a/layout/painting/FrameLayerBuilder.h +++ b/layout/painting/FrameLayerBuilder.h @@ -285,6 +285,7 @@ struct ContainerLayerParameters { : mXScale(aXScale), mYScale(aYScale), mLayerContentsVisibleRect(nullptr), + mItemVisibleRect(nullptr), mBackgroundColor(NS_RGBA(0, 0, 0, 0)), mScrollMetadataASR(nullptr), mCompositorASR(nullptr), @@ -298,6 +299,7 @@ struct ContainerLayerParameters { : mXScale(aXScale), mYScale(aYScale), mLayerContentsVisibleRect(nullptr), + mItemVisibleRect(nullptr), mOffset(aOffset), mBackgroundColor(aParent.mBackgroundColor), mScrollMetadataASR(aParent.mScrollMetadataASR), @@ -320,6 +322,11 @@ struct ContainerLayerParameters { */ nsIntRect* mLayerContentsVisibleRect; + /** + * If non-null, the rectangle which stores the item's visible rect. + */ + nsRect* mItemVisibleRect; + /** * An offset to apply to all child layers created. */ From d4e1195aed11b8ceda642d28cf93cfe718c7e461 Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 11 Jun 2019 07:52:12 -0500 Subject: [PATCH 5/8] Bug 1558482 - Apply visibleRect and scaling when painting. r=mattwoodrow visibleRect should affect both WR/layers, and scaling will only affect layers. Differential Revision: https://phabricator.services.mozilla.com/D34525 --HG-- extra : rebase_source : 49273db3e10088c9493f693d5f8b58864dcb01aa --- dom/ipc/BrowserChild.cpp | 12 ++++++++++ dom/ipc/BrowserChild.h | 4 ++++ layout/base/nsLayoutUtils.cpp | 13 ++++++++-- layout/painting/RetainedDisplayListBuilder.h | 6 ++++- layout/painting/nsDisplayList.cpp | 25 +++++++++++++------- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index b6fa72a97e25..148e2870e02e 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -60,6 +60,7 @@ #include "mozilla/TextEvents.h" #include "mozilla/TouchEvents.h" #include "mozilla/Unused.h" +#include "Units.h" #include "nsBrowserStatusFilter.h" #include "nsContentUtils.h" #include "nsDocShell.h" @@ -3328,6 +3329,17 @@ ScreenIntSize BrowserChild::GetInnerSize() { innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims); }; +nsRect BrowserChild::GetVisibleRect() { + bool isForceRendering = mIsTopLevel && mRenderLayers; + if (isForceRendering && !mEffectsInfo.IsVisible()) { + // We are forced to render even though we are not visible. In this case, we + // don't have an accurate visible rect, so we must be conservative. + return nsRect(nsPoint(), CSSPixel::ToAppUnits(mUnscaledInnerSize)); + } else { + return mEffectsInfo.mVisibleRect; + } +} + ScreenIntRect BrowserChild::GetOuterRect() { LayoutDeviceIntRect outerRect = RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale()); diff --git a/dom/ipc/BrowserChild.h b/dom/ipc/BrowserChild.h index f307e28ea2d5..64a30d25a585 100644 --- a/dom/ipc/BrowserChild.h +++ b/dom/ipc/BrowserChild.h @@ -418,6 +418,8 @@ class BrowserChild final : public nsMessageManagerScriptExecutor, bool IsTransparent() const { return mIsTransparent; } + const EffectsInfo& GetEffectsInfo() const { return mEffectsInfo; } + void GetMaxTouchPoints(uint32_t* aTouchPoints) { *aTouchPoints = mMaxTouchPoints; } @@ -547,6 +549,8 @@ class BrowserChild final : public nsMessageManagerScriptExecutor, ScreenIntSize GetInnerSize(); + nsRect GetVisibleRect(); + // Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow(). void DoFakeShow(const ShowInfo& aShowInfo); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 36c4099d19df..3dca9ecaf39a 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -26,6 +26,7 @@ #include "mozilla/StaticPrefs.h" #include "mozilla/Unused.h" #include "nsCharTraits.h" +#include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "nsFontMetrics.h" @@ -3786,12 +3787,20 @@ nsresult nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, builder->SetInActiveDocShell(isActive); } + nsRect rootVisualOverflow = aFrame->GetVisualOverflowRectRelativeToSelf(); + + // If we are in a remote browser, then apply clipping from ancestor browsers + if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { + rootVisualOverflow.IntersectRect(rootVisualOverflow, + browserChild->GetVisibleRect()); + } + nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); if (rootScrollFrame && !aFrame->GetParent()) { nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable(); MOZ_ASSERT(rootScrollableFrame); - nsRect displayPortBase = aFrame->GetVisualOverflowRectRelativeToSelf(); + nsRect displayPortBase = rootVisualOverflow; nsRect temp = displayPortBase; Unused << rootScrollableFrame->DecideScrollableLayer( builder, &displayPortBase, &temp, @@ -3806,7 +3815,7 @@ nsresult nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, // |ignoreViewportScrolling| and |usingDisplayPort| are persistent // document-rendering state. We rely on PresShell to flush // retained layers as needed when that persistent state changes. - visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf(); + visibleRegion = rootVisualOverflow; } else { visibleRegion = aDirtyRegion; } diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h index ffd79f2b7a6d..3d5418a06653 100644 --- a/layout/painting/RetainedDisplayListBuilder.h +++ b/layout/painting/RetainedDisplayListBuilder.h @@ -101,7 +101,8 @@ enum class PartialUpdateFailReason { RebuildLimit, FrameType, Disabled, - Content + Content, + VisibleRect, }; struct RetainedDisplayListMetrics { @@ -146,6 +147,8 @@ struct RetainedDisplayListMetrics { return "Disabled"; case PartialUpdateFailReason::Content: return "Content"; + case PartialUpdateFailReason::VisibleRect: + return "VisibleRect"; default: MOZ_ASSERT_UNREACHABLE("Enum value not handled!"); } @@ -254,6 +257,7 @@ struct RetainedDisplayListBuilder { nsDisplayListBuilder mBuilder; RetainedDisplayList mList; + nsRect mPreviousVisibleRect; WeakFrame mPreviousCaret; RetainedDisplayListMetrics mMetrics; }; diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index bc9ead4dbc2e..56b470d0ceb5 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -2882,11 +2882,19 @@ FrameLayerBuilder* nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder, rootLayer->SetScrollMetadata(nsTArray()); } - float rootLayerResolution = StaticPrefs::LayoutUseContainersForRootFrames() - ? presShell->GetResolution() - : 1.0f; - ContainerLayerParameters containerParameters(rootLayerResolution, - rootLayerResolution); + float resolutionUniform = StaticPrefs::LayoutUseContainersForRootFrames() + ? presShell->GetResolution() + : 1.0f; + float resolutionX = resolutionUniform; + float resolutionY = resolutionUniform; + + // If we are in a remote browser, then apply scaling from ancestor browsers + if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { + resolutionX *= browserChild->GetEffectsInfo().mScaleX; + resolutionY *= browserChild->GetEffectsInfo().mScaleY; + } + + ContainerLayerParameters containerParameters(resolutionX, resolutionY); { PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); @@ -2906,11 +2914,10 @@ FrameLayerBuilder* nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder, if (!root) { return nullptr; } + // Root is being scaled up by the X/Y resolution. Scale it back down. + root->SetPostScale(1.0f / resolutionX, 1.0f / resolutionY); if (StaticPrefs::LayoutUseContainersForRootFrames()) { - // Root is being scaled up by the X/Y resolution. Scale it back down. - root->SetPostScale(1.0f / containerParameters.mXScale, - 1.0f / containerParameters.mYScale); - root->SetScaleToResolution(containerParameters.mXScale); + root->SetScaleToResolution(resolutionUniform); } auto callback = [root](ScrollableLayerGuid::ViewID aScrollId) -> bool { From b76533bae05849613a6977733b48472e1e494371 Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 11 Jun 2019 07:57:23 -0500 Subject: [PATCH 6/8] Bug 1558482 - Only initialize the root displayport for the cross process root content document. r=kats The root displayport has some assumptions built into it about being positioned at the origin and sized to the composition bounds that seem like they only apply to the cross process root content document. This commit changes us to avoid taking this code path for OOP-iframes. Differential Revision: https://phabricator.services.mozilla.com/D34527 --HG-- extra : rebase_source : 026bb84b7ad086f508228620d19d9f459f28bf1d --- dom/ipc/BrowserChild.cpp | 2 +- gfx/layers/apz/util/APZCCallbackHelper.cpp | 2 +- layout/generic/nsGfxScrollFrame.cpp | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 148e2870e02e..0f3610de9e83 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -437,7 +437,7 @@ BrowserChild::Observe(nsISupports* aSubject, const char* aTopic, nsCOMPtr subject(do_QueryInterface(aSubject)); nsCOMPtr doc(GetTopLevelDocument()); - if (subject == doc) { + if (subject == doc && doc->IsTopLevelContentDocument()) { RefPtr presShell = doc->GetPresShell(); if (presShell) { presShell->SetIsFirstPaint(true); diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp index f6ba2a6099ed..bf7927d61947 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -406,7 +406,7 @@ void APZCCallbackHelper::InitializeRootDisplayport(PresShell* aPresShell) { &viewId)) { nsPresContext* pc = aPresShell->GetPresContext(); // This code is only correct for root content or toplevel documents. - MOZ_ASSERT(!pc || pc->IsRootContentDocument() || + MOZ_ASSERT(!pc || pc->IsRootContentDocumentCrossProcess() || !pc->GetParentPresContext()); nsIFrame* frame = aPresShell->GetRootScrollFrame(); if (!frame) { diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 4b1dfb86d455..e412660a5e30 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -3801,8 +3801,12 @@ bool ScrollFrameHelper::DecideScrollableLayer( if (aSetBase) { nsRect displayportBase = *aVisibleRect; nsPresContext* pc = mOuter->PresContext(); - if (mIsRoot && - (pc->IsRootContentDocument() || !pc->GetParentPresContext())) { + + bool isContentRootDoc = pc->IsRootContentDocumentCrossProcess(); + bool isChromeRootDoc = + !pc->Document()->IsContentDocument() && !pc->GetParentPresContext(); + + if (mIsRoot && (isContentRootDoc || isChromeRootDoc)) { displayportBase = nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter)); From d7b4b6a1a4578d14c3f659a845015a08a9513dbe Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 11 Jun 2019 07:57:52 -0500 Subject: [PATCH 7/8] Bug 1558482 - Restrict the composition size to the visibleRect for OOP-iframes. r=kats Even if we don't have a root displayport, the composition size is still used for displayport margins calculations. For extremely tall iframes, this will create a displayport that is way to big. We should instead report a composition size that is equivalent to the visible rect for OOP-iframes. Differential Revision: https://phabricator.services.mozilla.com/D34528 --HG-- extra : rebase_source : 1f43cb16a0841eb3cd892401c83a46d56276cf2e --- layout/base/nsLayoutUtils.cpp | 5 ++--- widget/PuppetWidget.cpp | 9 +++++++++ widget/PuppetWidget.h | 2 ++ widget/nsIWidget.h | 10 ++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 3dca9ecaf39a..3af702663720 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -8474,10 +8474,9 @@ static bool UpdateCompositionBoundsForRCDRSF(ParentLayerRect& aCompBounds, #endif if (widget) { - LayoutDeviceIntRect widgetBounds = widget->GetBounds(); - widgetBounds.MoveTo(0, 0); aCompBounds = ParentLayerRect(ViewAs( - widgetBounds, + LayoutDeviceIntRect(LayoutDeviceIntPoint(), + widget->GetCompositionSize()), PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF)); return true; } diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp index 5a7f448d31eb..c534c04396cd 100644 --- a/widget/PuppetWidget.cpp +++ b/widget/PuppetWidget.cpp @@ -1158,6 +1158,15 @@ LayoutDeviceIntRect PuppetWidget::GetScreenBounds() { return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size()); } +LayoutDeviceIntSize PuppetWidget::GetCompositionSize() { + if (!mBrowserChild) { + return nsBaseWidget::GetCompositionSize(); + } + CSSSize visibleSize = + CSSPixel::FromAppUnits(mBrowserChild->GetVisibleRect().Size()); + return RoundedToInt(visibleSize * GetDefaultScale()); +} + uint32_t PuppetWidget::GetMaxTouchPoints() const { uint32_t maxTouchPoints = 0; if (mBrowserChild) { diff --git a/widget/PuppetWidget.h b/widget/PuppetWidget.h index 85a3862783a7..a05a069703af 100644 --- a/widget/PuppetWidget.h +++ b/widget/PuppetWidget.h @@ -242,6 +242,8 @@ class PuppetWidget : public nsBaseWidget, virtual LayoutDeviceIntRect GetScreenBounds() override; + virtual LayoutDeviceIntSize GetCompositionSize() override; + virtual MOZ_MUST_USE nsresult StartPluginIME( const mozilla::WidgetKeyboardEvent& aKeyboardEvent, int32_t aPanelX, int32_t aPanelY, nsString& aCommitted) override; diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h index 1c6245dab267..6ab94cdfb9a5 100644 --- a/widget/nsIWidget.h +++ b/widget/nsIWidget.h @@ -937,6 +937,16 @@ class nsIWidget : public nsISupports { return GetClientBounds().Size(); } + /** + * Get the size of the bounds of this widget that will be visible when + * rendered. + * + * @return the width and height of the composition size of this widget. + */ + virtual LayoutDeviceIntSize GetCompositionSize() { + return GetBounds().Size(); + } + /** * Set the background color for this widget * From 73bc7c8dc2acf2efb22f72317e818acd4dc3657f Mon Sep 17 00:00:00 2001 From: Jan Varga Date: Thu, 13 Jun 2019 04:17:52 +0200 Subject: [PATCH 8/8] Bug 1559029 - LSNG: Connection::FlushOp::DoDatastoreWork needs to automatically rollback the transaction and detach the shadow database on an error; r=asuth Differential Revision: https://phabricator.services.mozilla.com/D34814 --- dom/localstorage/ActorsParent.cpp | 296 ++++++++++++++------ dom/localstorage/test/unit/head.js | 12 + dom/localstorage/test/unit/test_flushing.js | 67 +++++ dom/localstorage/test/unit/xpcshell.ini | 1 + 4 files changed, 295 insertions(+), 81 deletions(-) create mode 100644 dom/localstorage/test/unit/test_flushing.js diff --git a/dom/localstorage/ActorsParent.cpp b/dom/localstorage/ActorsParent.cpp index bcce87ba22ee..f0374271a1a2 100644 --- a/dom/localstorage/ActorsParent.cpp +++ b/dom/localstorage/ActorsParent.cpp @@ -1433,6 +1433,12 @@ class Connection final { void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); } + QuotaClient* GetQuotaClient() const { + MOZ_ASSERT(mQuotaClient); + + return mQuotaClient; + } + ArchivedOriginScope* GetArchivedOriginScope() const { return mArchivedOriginScope; } @@ -1479,6 +1485,12 @@ class Connection final { nsresult GetCachedStatement(const nsACString& aQuery, CachedStatement* aCachedStatement); + nsresult BeginWriteTransaction(); + + nsresult CommitWriteTransaction(); + + nsresult RollbackWriteTransaction(); + private: // Only created by ConnectionThread. Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix, @@ -2819,6 +2831,30 @@ class QuotaClient::MatchFunction final : public mozIStorageFunction { NS_DECL_MOZISTORAGEFUNCTION }; +/******************************************************************************* + * Helper classes + ******************************************************************************/ + +class MOZ_STACK_CLASS AutoWriteTransaction final { + Connection* mConnection; + Maybe mShadowDatabaseLock; + bool mShadowWrites; + + public: + explicit AutoWriteTransaction(bool aShadowWrites); + + ~AutoWriteTransaction(); + + nsresult Start(Connection* aConnection); + + nsresult Commit(); + + private: + nsresult LockAndAttachShadowDatabase(Connection* aConnection); + + nsresult DetachShadowDatabaseAndUnlock(); +}; + /******************************************************************************* * Globals ******************************************************************************/ @@ -4342,6 +4378,60 @@ nsresult Connection::GetCachedStatement(const nsACString& aQuery, return NS_OK; } +nsresult Connection::BeginWriteTransaction() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mStorageConnection); + + CachedStatement stmt; + nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), + &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult Connection::CommitWriteTransaction() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mStorageConnection); + + CachedStatement stmt; + nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult Connection::RollbackWriteTransaction() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mStorageConnection); + + CachedStatement stmt; + nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &stmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // This may fail if SQLite already rolled back the transaction so ignore any + // errors. + Unused << stmt->Execute(); + + return NS_OK; +} + void Connection::ScheduleFlush() { AssertIsOnOwningThread(); MOZ_ASSERT(mWriteOptimizer.HasWrites()); @@ -4497,69 +4587,9 @@ nsresult Connection::FlushOp::DoDatastoreWork() { AssertIsOnConnectionThread(); MOZ_ASSERT(mConnection); - class MOZ_STACK_CLASS AutoDetach final { - nsCOMPtr mConnection; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + AutoWriteTransaction autoWriteTransaction(mShadowWrites); - public: - explicit AutoDetach( - mozIStorageConnection* aConnection MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : mConnection(aConnection) { - MOZ_ASSERT(aConnection); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } - - ~AutoDetach() { - if (mConnection) { - nsresult rv = DetachShadowDatabase(mConnection); - Unused << NS_WARN_IF(NS_FAILED(rv)); - } - } - - void release() { mConnection = nullptr; } - - private: - explicit AutoDetach(const AutoDetach&) = delete; - AutoDetach& operator=(const AutoDetach&) = delete; - AutoDetach& operator=(AutoDetach&&) = delete; - }; - - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - - nsCOMPtr storageConnection = - mConnection->StorageConnection(); - MOZ_ASSERT(storageConnection); - - nsresult rv; - - Maybe shadowDatabaseLock; - - Maybe autoDetach; - - if (mShadowWrites) { - MOZ_ASSERT(mConnection->mQuotaClient); - - shadowDatabaseLock.emplace( - mConnection->mQuotaClient->ShadowDatabaseMutex()); - - rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - autoDetach.emplace(storageConnection); - } - - CachedStatement stmt; - rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), - &stmt); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - rv = stmt->Execute(); + nsresult rv = autoWriteTransaction.Start(mConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -4588,34 +4618,19 @@ nsresult Connection::FlushOp::DoDatastoreWork() { return rv; } - rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt); + rv = autoWriteTransaction.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = stmt->Execute(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - if (mShadowWrites) { - autoDetach->release(); - - rv = DetachShadowDatabase(storageConnection); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - autoDetach.reset(); - - shadowDatabaseLock.reset(); - } - rv = usageJournalFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + RefPtr runnable = NS_NewRunnableFunction("dom::localstorage::UpdateUsageRunnable", [origin = mConnection->Origin(), usage]() { @@ -9351,5 +9366,124 @@ QuotaClient::MatchFunction::OnFunctionCall( return NS_OK; } +/******************************************************************************* + * AutoWriteTransaction + ******************************************************************************/ + +AutoWriteTransaction::AutoWriteTransaction(bool aShadowWrites) + : mConnection(nullptr) + , mShadowWrites(aShadowWrites) +{ + AssertIsOnConnectionThread(); + + MOZ_COUNT_CTOR(mozilla::dom::AutoWriteTransaction); +} + +AutoWriteTransaction::~AutoWriteTransaction() { + AssertIsOnConnectionThread(); + + MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction); + + if (mConnection) { + if (NS_FAILED(mConnection->RollbackWriteTransaction())) { + NS_WARNING("Failed to rollback write transaction!"); + } + + if (mShadowWrites && NS_FAILED(DetachShadowDatabaseAndUnlock())) { + NS_WARNING("Failed to detach shadow database!"); + } + } +} + +nsresult AutoWriteTransaction::Start(Connection* aConnection) { + AssertIsOnConnectionThread(); + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!mConnection); + + nsresult rv; + + if (mShadowWrites) { + rv = LockAndAttachShadowDatabase(aConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = aConnection->BeginWriteTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mConnection = aConnection; + + return NS_OK; +} + +nsresult AutoWriteTransaction::Commit() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mConnection); + + nsresult rv = mConnection->CommitWriteTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mShadowWrites) { + rv = DetachShadowDatabaseAndUnlock(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mConnection = nullptr; + + return NS_OK; +} + +nsresult AutoWriteTransaction::LockAndAttachShadowDatabase(Connection* aConnection) { + AssertIsOnConnectionThread(); + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!mConnection); + MOZ_ASSERT(mShadowDatabaseLock.isNothing()); + MOZ_ASSERT(mShadowWrites); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsCOMPtr storageConnection = + aConnection->StorageConnection(); + MOZ_ASSERT(storageConnection); + + mShadowDatabaseLock.emplace( + aConnection->GetQuotaClient()->ShadowDatabaseMutex()); + + nsresult rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult AutoWriteTransaction::DetachShadowDatabaseAndUnlock() { + AssertIsOnConnectionThread(); + MOZ_ASSERT(mConnection); + MOZ_ASSERT(mShadowDatabaseLock.isSome()); + MOZ_ASSERT(mShadowWrites); + + nsCOMPtr storageConnection = + mConnection->StorageConnection(); + MOZ_ASSERT(storageConnection); + + nsresult rv = DetachShadowDatabase(storageConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mShadowDatabaseLock.reset(); + + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/localstorage/test/unit/head.js b/dom/localstorage/test/unit/head.js index a1dcfc65d7cb..bf9da017931d 100644 --- a/dom/localstorage/test/unit/head.js +++ b/dom/localstorage/test/unit/head.js @@ -85,6 +85,18 @@ function resetOriginLimit() { Services.prefs.clearUserPref("dom.storage.default_quota"); } +function setTimeout(callback, timeout) { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + timer.initWithCallback({ + notify() { + callback(); + }, + }, timeout, Ci.nsITimer.TYPE_ONE_SHOT); + + return timer; +} + function init() { let request = Services.qms.init(); diff --git a/dom/localstorage/test/unit/test_flushing.js b/dom/localstorage/test/unit/test_flushing.js new file mode 100644 index 000000000000..101a507e2961 --- /dev/null +++ b/dom/localstorage/test/unit/test_flushing.js @@ -0,0 +1,67 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +/** + * This test is mainly to verify that the flush operation detaches the shadow + * database in the event of early return due to error. See bug 1559029. + */ + +async function testSteps() { + const principal1 = getPrincipal("http://example1.com"); + + const usageFile1 = + getRelativeFile("storage/default/http+++example1.com/ls/usage"); + + const principal2 = getPrincipal("http://example2.com"); + + const data = { + key: "foo", + value: "bar", + }; + + const flushSleepTimeSec = 6; + + info("Setting prefs"); + + Services.prefs.setBoolPref("dom.storage.next_gen", true); + + info("Getting storage 1"); + + let storage1 = getLocalStorage(principal1); + + info("Adding item"); + + storage1.setItem(data.key, data.value); + + info("Creating usage as a directory"); + + // This will cause a failure during the flush for first principal. + usageFile1.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); + + info("Getting storage 2"); + + let storage2 = getLocalStorage(principal2); + + info("Adding item"); + + storage2.setItem(data.key, data.value); + + // The flush for second principal shouldn't be affected by failed flush for + // first principal. + + info("Sleeping for " + flushSleepTimeSec + " seconds to let all flushes " + + "finish"); + + await new Promise(function(resolve) { + setTimeout(resolve, flushSleepTimeSec * 1000); + }); + + info("Resetting"); + + // Wait for all database connections to close. + let request = reset(); + await requestFinished(request); +} diff --git a/dom/localstorage/test/unit/xpcshell.ini b/dom/localstorage/test/unit/xpcshell.ini index c490897c8e41..194d71d9ebe9 100644 --- a/dom/localstorage/test/unit/xpcshell.ini +++ b/dom/localstorage/test/unit/xpcshell.ini @@ -33,6 +33,7 @@ run-sequentially = test_databaseShadowing_clearOriginsByPrefix2.js depends on a [test_databaseShadowing_clearOriginsByPrefix2.js] run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js [test_eviction.js] +[test_flushing.js] [test_groupLimit.js] [test_groupMismatch.js] [test_largeItems.js]