author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Mon, 08 Jun 2015 11:55:30 +0200 | |
changeset 247584 | 4700d1cdf489a05844f3ec5b66c550da40c7cb1f |
parent 247583 | 92926c0d8c8af1ae1ed795576642a95da56efb3b (current diff) |
parent 247526 | edaec128d6ecec869a03fafd3390cd493eb254c5 (diff) |
child 247585 | 9ef529a9a02b54375bf4a025612201508629a48f |
child 247670 | 64d4c71b2def2e75a9a7d6e0cefd561664d0d5fe |
child 247691 | 7011b4d65de3ff7800574b66ea1708c34022e5bc |
child 247709 | 5507b3f15d4ca69bb25a98adbd38e68befe03989 |
push id | 60743 |
push user | cbook@mozilla.com |
push date | Mon, 08 Jun 2015 10:03:01 +0000 |
treeherder | mozilla-inbound@9ef529a9a02b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 41.0a1 |
first release with | nightly linux32
4700d1cdf489
/
41.0a1
/
20150608030201
/
files
nightly linux64
4700d1cdf489
/
41.0a1
/
20150608030201
/
files
nightly mac
4700d1cdf489
/
41.0a1
/
20150608030201
/
files
nightly win32
4700d1cdf489
/
41.0a1
/
20150608030201
/
files
nightly win64
4700d1cdf489
/
41.0a1
/
20150608030201
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
41.0a1
/
20150608030201
/
pushlog to previous
nightly linux64
41.0a1
/
20150608030201
/
pushlog to previous
nightly mac
41.0a1
/
20150608030201
/
pushlog to previous
nightly win32
41.0a1
/
20150608030201
/
pushlog to previous
nightly win64
41.0a1
/
20150608030201
/
pushlog to previous
|
ipc/chromium/src/base/third_party/purify/pure.h | file | annotate | diff | comparison | revisions | |
ipc/chromium/src/base/third_party/purify/pure_api.c | file | annotate | diff | comparison | revisions | |
security/sandbox/linux/SandboxAssembler.cpp | file | annotate | diff | comparison | revisions | |
security/sandbox/linux/SandboxAssembler.h | file | annotate | diff | comparison | revisions | |
xpcom/base/pure.h | file | annotate | diff | comparison | revisions | |
xpcom/base/pure_api.c | file | annotate | diff | comparison | revisions |
--- a/accessible/atk/AccessibleWrap.cpp +++ b/accessible/atk/AccessibleWrap.cpp @@ -795,21 +795,22 @@ getParentCB(AtkObject *aAtkObj) return aAtkObj->accessible_parent; AtkObject* atkParent = nullptr; if (AccessibleWrap* wrapper = GetAccessibleWrap(aAtkObj)) { Accessible* parent = wrapper->Parent(); atkParent = parent ? AccessibleWrap::GetAtkObject(parent) : nullptr; } else if (ProxyAccessible* proxy = GetProxy(aAtkObj)) { ProxyAccessible* parent = proxy->Parent(); - if (parent) + if (parent) { atkParent = GetWrapperFor(parent); - - // Otherwise this should be the proxy for the tab's top level document. - atkParent = AccessibleWrap::GetAtkObject(proxy->OuterDocOfRemoteBrowser()); + } else { + // Otherwise this should be the proxy for the tab's top level document. + atkParent = AccessibleWrap::GetAtkObject(proxy->OuterDocOfRemoteBrowser()); + } } if (atkParent) atk_object_set_parent(aAtkObj, atkParent); return aAtkObj->accessible_parent; }
--- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -451,17 +451,17 @@ DocAccessible::Shutdown() int32_t childDocCount = mChildDocuments.Length(); for (int32_t idx = childDocCount - 1; idx >= 0; idx--) mChildDocuments[idx]->Shutdown(); mChildDocuments.Clear(); // XXX thinking about ordering? if (IPCAccessibilityActive()) { - DocAccessibleChild::Send__delete__(mIPCDoc); + mIPCDoc->Shutdown(); MOZ_ASSERT(!mIPCDoc); } if (mVirtualCursor) { mVirtualCursor->RemoveObserver(this); mVirtualCursor = nullptr; }
--- a/accessible/ipc/DocAccessibleChild.cpp +++ b/accessible/ipc/DocAccessibleChild.cpp @@ -65,16 +65,19 @@ SerializeTree(Accessible* aRoot, nsTArra } Accessible* DocAccessibleChild::IdToAccessible(const uint64_t& aID) const { if (!aID) return mDoc; + if (!mDoc) + return nullptr; + return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID)); } Accessible* DocAccessibleChild::IdToAccessibleLink(const uint64_t& aID) const { Accessible* acc = IdToAccessible(aID); return acc && acc->IsLink() ? acc : nullptr; @@ -220,17 +223,17 @@ DocAccessibleChild::PersistentProperties return true; } bool DocAccessibleChild::RecvRelationByType(const uint64_t& aID, const uint32_t& aType, nsTArray<uint64_t>* aTargets) { - Accessible* acc = mDoc->GetAccessibleByUniqueID((void*)aID); + Accessible* acc = IdToAccessible(aID); if (!acc) return true; auto type = static_cast<RelationType>(aType); Relation rel = acc->RelationByType(type); while (Accessible* target = rel.Next()) aTargets->AppendElement(reinterpret_cast<uintptr_t>(target)); @@ -253,17 +256,17 @@ AddRelation(Accessible* aAcc, RelationTy newRelation->Targets().SwapElements(targets); } } bool DocAccessibleChild::RecvRelations(const uint64_t& aID, nsTArray<RelationTargets>* aRelations) { - Accessible* acc = mDoc->GetAccessibleByUniqueID((void*)aID); + Accessible* acc = IdToAccessible(aID); if (!acc) return true; #define RELATIONTYPE(gecko, s, a, m, i) AddRelation(acc, RelationType::gecko, aRelations); #include "RelationTypeMap.h" #undef RELATIONTYPE
--- a/accessible/ipc/DocAccessibleChild.h +++ b/accessible/ipc/DocAccessibleChild.h @@ -27,18 +27,29 @@ class AccShowEvent; class DocAccessibleChild : public PDocAccessibleChild { public: explicit DocAccessibleChild(DocAccessible* aDoc) : mDoc(aDoc) { MOZ_COUNT_CTOR(DocAccessibleChild); } ~DocAccessibleChild() { + // Shutdown() should have been called, but maybe it isn't if the process is + // killed? + MOZ_ASSERT(!mDoc); + if (mDoc) + mDoc->SetIPCDoc(nullptr); + MOZ_COUNT_DTOR(DocAccessibleChild); + } + + void Shutdown() + { mDoc->SetIPCDoc(nullptr); - MOZ_COUNT_DTOR(DocAccessibleChild); + mDoc = nullptr; + SendShutdown(); } void ShowEvent(AccShowEvent* aShowEvent); /* * Return the state for the accessible with given ID. */ virtual bool RecvState(const uint64_t& aID, uint64_t* aState) override;
--- a/accessible/ipc/DocAccessibleParent.cpp +++ b/accessible/ipc/DocAccessibleParent.cpp @@ -3,16 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DocAccessibleParent.h" #include "nsAutoPtr.h" #include "mozilla/a11y/Platform.h" #include "ProxyAccessible.h" +#include "mozilla/dom/TabParent.h" namespace mozilla { namespace a11y { bool DocAccessibleParent::RecvShowEvent(const ShowEventData& aData) { if (mShutdown) @@ -181,16 +182,28 @@ DocAccessibleParent::AddChildDoc(DocAcce PLDHashOperator DocAccessibleParent::ShutdownAccessibles(ProxyEntry* entry, void*) { ProxyDestroyed(entry->mProxy); return PL_DHASH_REMOVE; } +bool +DocAccessibleParent::RecvShutdown() +{ + Destroy(); + + if (!static_cast<dom::TabParent*>(Manager())->IsDestroyed()) { + return PDocAccessibleParent::Send__delete__(this); + } + + return true; +} + void DocAccessibleParent::Destroy() { NS_ASSERTION(mChildDocs.IsEmpty(), "why weren't the child docs destroyed already?"); MOZ_ASSERT(!mShutdown); mShutdown = true;
--- a/accessible/ipc/DocAccessibleParent.h +++ b/accessible/ipc/DocAccessibleParent.h @@ -54,16 +54,17 @@ public: virtual bool RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override; void Unbind() { mParent = nullptr; ParentDoc()->mChildDocs.RemoveElement(this); mParentDoc = nullptr; } + virtual bool RecvShutdown() override; void Destroy(); virtual void ActorDestroy(ActorDestroyReason aWhy) override { if (!mShutdown) Destroy(); } /*
--- a/accessible/ipc/PDocAccessible.ipdl +++ b/accessible/ipc/PDocAccessible.ipdl @@ -43,17 +43,17 @@ struct RelationTargets uint64_t[] Targets; }; prio(normal upto high) sync protocol PDocAccessible { manager PBrowser; parent: - __delete__(); + Shutdown(); /* * Notify the parent process the document in the child process is firing an * event. */ Event(uint64_t aID, uint32_t type); ShowEvent(ShowEventData data); HideEvent(uint64_t aRootID); @@ -62,16 +62,18 @@ parent: /* * Tell the parent document to bind the existing document as a new child * document. */ BindChildDoc(PDocAccessible aChildDoc, uint64_t aID); child: + __delete__(); + // Accessible prio(high) sync State(uint64_t aID) returns(uint64_t states); prio(high) sync Name(uint64_t aID) returns(nsString name); prio(high) sync Value(uint64_t aID) returns(nsString value); prio(high) sync Description(uint64_t aID) returns(nsString desc); prio(high) sync Attributes(uint64_t aID) returns(Attribute[] attributes); prio(high) sync RelationByType(uint64_t aID, uint32_t aRelationType) returns(uint64_t[] targets);
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1919,8 +1919,13 @@ pref("reader.parse-node-limit", 0); pref("browser.pocket.enabled", true); pref("browser.pocket.api", "api.getpocket.com"); pref("browser.pocket.site", "getpocket.com"); pref("browser.pocket.oAuthConsumerKey", "40249-e88c401e1b1f2242d9e441c4"); 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); +#endif
index bfc718af6536e811bbfd6ed242da91f48df47f1a..2835ad347018d1ab8caa04eda40603bd781d781c GIT binary patch literal 65536 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 HAgKHgAd%oe
index bc41b88d88c852ef5ce9490e45df28918de115c7..ae47698b30a2afe073d9b7d4bdf3bc094794a346 GIT binary patch literal 114688 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≈ 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
--- a/build/pgo/server-locations.txt +++ b/build/pgo/server-locations.txt @@ -91,16 +91,17 @@ http://sub1.test2.example.com:80 pri http://sub2.test1.example.com:80 privileged http://sub2.test2.example.com:80 privileged http://noxul.example.com:80 privileged,noxul http://example.net:80 privileged # Used to test that clearing Service Workers for domain example.com, does not clear prefixexample.com http://prefixexample.com:80 https://example.com:443 privileged +https://example.org:443 privileged https://test1.example.com:443 privileged https://test2.example.com:443 privileged https://sub1.test1.example.com:443 privileged https://sub1.test2.example.com:443 privileged https://sub2.test1.example.com:443 privileged https://sub2.test2.example.com:443 privileged https://nocert.example.com:443 privileged,nocert https://self-signed.example.com:443 privileged,cert=selfsigned
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -242,16 +242,17 @@ nsString* nsContentUtils::sControlText = nsString* nsContentUtils::sMetaText = nullptr; nsString* nsContentUtils::sOSText = nullptr; nsString* nsContentUtils::sAltText = nullptr; nsString* nsContentUtils::sModifierSeparator = nullptr; bool nsContentUtils::sInitialized = false; 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; uint32_t nsContentUtils::sHandlingInputTimeout = 1000; @@ -509,16 +510,19 @@ nsContentUtils::Init() "dom.allow_XUL_XBL_for_file"); Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled, "full-screen-api.enabled"); Preferences::AddBoolVarCache(&sTrustedFullScreenOnly, "full-screen-api.allow-trusted-requests-only"); + Preferences::AddBoolVarCache(&sIsCutCopyAllowed, + "dom.allow_cut_copy", true); + Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled, "dom.enable_performance", true); Preferences::AddBoolVarCache(&sIsResourceTimingEnabled, "dom.enable_resource_timing", true); Preferences::AddBoolVarCache(&sIsUserTimingLoggingEnabled, "dom.performance.enable_user_timing_logging", false); @@ -6627,17 +6631,18 @@ nsContentUtils::IsRequestFullScreenAllow EventStateManager::IsHandlingUserInput() || IsCallerChrome(); } /* static */ bool nsContentUtils::IsCutCopyAllowed() { - return EventStateManager::IsHandlingUserInput() || + return (!IsCutCopyRestricted() && + EventStateManager::IsHandlingUserInput()) || IsCallerChrome(); } /* static */ bool nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2) { if (!aDoc1 || !aDoc2) {
--- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1859,16 +1859,25 @@ public: * Returns true if requests for full-screen are allowed in the current * context. Requests are only allowed if the user initiated them (like with * a mouse-click or key press), unless this check has been disabled by * setting the pref "full-screen-api.allow-trusted-requests-only" to false. */ static bool IsRequestFullScreenAllowed(); /** + * Returns true if calling execCommand with 'cut' or 'copy' arguments + * is restricted to chrome code. + */ + static bool IsCutCopyRestricted() + { + return !sIsCutCopyAllowed; + } + + /** * Returns true if calling execCommand with 'cut' or 'copy' arguments is * allowed in the current context. These are only allowed if the user initiated * them (like with a mouse-click or key press). */ static bool IsCutCopyAllowed(); /* * Returns true if the performance timing APIs are enabled. @@ -2429,16 +2438,17 @@ private: static uint32_t sScriptBlockerCountWhereRunnersPrevented; static nsIInterfaceRequestor* sSameOriginChecker; static bool sIsHandlingKeyBoardEvent; static bool sAllowXULXBL_for_file; static bool sIsFullScreenApiEnabled; 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 nsHtml5StringParser* sHTMLFragmentParser;
--- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -24,21 +24,21 @@ #include "mozilla/dom/ResponseBinding.h" #include "nsIContentPolicy.h" namespace mozilla { namespace dom { namespace cache { namespace db { -const int32_t kMaxWipeSchemaVersion = 10; +const int32_t kMaxWipeSchemaVersion = 11; namespace { -const int32_t kLatestSchemaVersion = 10; +const int32_t kLatestSchemaVersion = 11; const int32_t kMaxEntriesPerStatement = 255; const uint32_t kPageSize = 4 * 1024; // Grow the database in chunks to reduce fragmentation const uint32_t kGrowthSize = 32 * 1024; const uint32_t kGrowthPages = kGrowthSize / kPageSize; static_assert(kGrowthSize % kPageSize == 0, @@ -296,16 +296,20 @@ CreateSchema(mozIStorageConnection* aCon "request_body_id TEXT NULL, " "response_type INTEGER NOT NULL, " "response_url TEXT NOT NULL, " "response_status INTEGER NOT NULL, " "response_status_text TEXT NOT NULL, " "response_headers_guard INTEGER NOT NULL, " "response_body_id TEXT NULL, " "response_security_info_id INTEGER NULL REFERENCES security_info(id), " + "response_redirected INTEGER NOT NULL, " + // Note that response_redirected_url is either going to be empty, or + // it's going to be a URL different than response_url. + "response_redirected_url TEXT NOT NULL, " "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE" ");" )); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // TODO: see if we can remove these indices on TEXT columns (bug 1110458) rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE INDEX entries_request_url_index " @@ -1463,16 +1467,18 @@ InsertEntry(mozIStorageConnection* aConn "request_body_id, " "response_type, " "response_url, " "response_status, " "response_status_text, " "response_headers_guard, " "response_body_id, " "response_security_info_id, " + "response_redirected, " + "response_redirected_url, " "cache_id " ") VALUES (" ":request_method, " ":request_url, " ":request_url_no_query, " ":request_referrer, " ":request_headers_guard, " ":request_mode, " @@ -1483,16 +1489,18 @@ InsertEntry(mozIStorageConnection* aConn ":request_body_id, " ":response_type, " ":response_url, " ":response_status, " ":response_status_text, " ":response_headers_guard, " ":response_body_id, " ":response_security_info_id, " + ":response_redirected, " + ":response_redirected_url, " ":cache_id " ");" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("request_method"), aRequest.method()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1562,16 +1570,24 @@ InsertEntry(mozIStorageConnection* aConn if (aResponse.channelInfo().securityInfo().IsEmpty()) { rv = state->BindNullByName(NS_LITERAL_CSTRING("response_security_info_id")); } else { rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_security_info_id"), securityId); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_redirected"), + aResponse.channelInfo().redirected() ? 1 : 0); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_redirected_url"), + aResponse.channelInfo().redirectedURI()); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = state->BindInt64ByName(NS_LITERAL_CSTRING("cache_id"), aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT last_insert_rowid()" @@ -1653,16 +1669,18 @@ ReadResponse(mozIStorageConnection* aCon nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "entries.response_type, " "entries.response_url, " "entries.response_status, " "entries.response_status_text, " "entries.response_headers_guard, " "entries.response_body_id, " + "entries.response_redirected, " + "entries.response_redirected_url, " "security_info.data " "FROM entries " "LEFT OUTER JOIN security_info " "ON entries.response_security_info_id=security_info.id " "WHERE entries.id=:id;" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1700,17 +1718,25 @@ ReadResponse(mozIStorageConnection* aCon if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedResponseOut->mHasBodyId = !nullBody; if (aSavedResponseOut->mHasBodyId) { rv = ExtractId(state, 5, &aSavedResponseOut->mBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } - rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo()); + int32_t redirected; + rv = state->GetInt32(6, &redirected); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + aSavedResponseOut->mValue.channelInfo().redirected() = !!redirected; + + rv = state->GetUTF8String(7, aSavedResponseOut->mValue.channelInfo().redirectedURI()); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->GetBlobAsUTF8String(8, aSavedResponseOut->mValue.channelInfo().securityInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "name, " "value " "FROM response_headers " "WHERE entry_id=:entry_id;"
--- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -8,16 +8,17 @@ #include "mozilla/IMEStateManager.h" #include "mozilla/TextEvents.h" #include "mozilla/dom/Element.h" #include "nsCaret.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsCopySupport.h" #include "nsFocusManager.h" +#include "nsFontMetrics.h" #include "nsFrameSelection.h" #include "nsIContentIterator.h" #include "nsIPresShell.h" #include "nsISelection.h" #include "nsISelectionController.h" #include "nsISelectionPrivate.h" #include "nsIDOMRange.h" #include "nsIFrame.h" @@ -1076,28 +1077,54 @@ ContentEventHandler::OnQueryCaretRect(Wi nsIFrame* frame; rv = GetStartFrameAndOffset(range, frame, xpOffsetInFrame); NS_ENSURE_SUCCESS(rv, rv); nsPoint posInFrame; rv = frame->GetPointFromOffset(range->StartOffset(), &posInFrame); NS_ENSURE_SUCCESS(rv, rv); + aEvent->mReply.mWritingMode = frame->GetWritingMode(); + bool isVertical = aEvent->mReply.mWritingMode.IsVertical(); + nsRect rect; rect.x = posInFrame.x; rect.y = posInFrame.y; - rect.width = caretRect.width; - rect.height = frame->GetSize().height; + + nscoord fontHeight = 0; + float inflation = nsLayoutUtils::FontSizeInflationFor(frame); + nsRefPtr<nsFontMetrics> fontMetrics; + rv = nsLayoutUtils::GetFontMetricsForFrame(frame, getter_AddRefs(fontMetrics), + inflation); + if (NS_WARN_IF(!fontMetrics)) { + // If we cannot get font height, use frame size instead. + fontHeight = isVertical ? frame->GetSize().width : frame->GetSize().height; + } else { + fontHeight = fontMetrics->MaxAscent() + fontMetrics->MaxDescent(); + } + if (isVertical) { + rect.width = fontHeight; + rect.height = caretRect.height; + } else { + rect.width = caretRect.width; + rect.height = fontHeight; + } rv = ConvertToRootViewRelativeOffset(frame, rect); NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped( rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel())); - aEvent->mReply.mWritingMode = frame->GetWritingMode(); + // If the caret rect is empty, let's make it non-empty rect. + if (!aEvent->mReply.mRect.width) { + aEvent->mReply.mRect.width = 1; + } + if (!aEvent->mReply.mRect.height) { + aEvent->mReply.mRect.height = 1; + } aEvent->mSucceeded = true; return NS_OK; } nsresult ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent) { nsresult rv = Init(aEvent);
--- a/dom/fetch/ChannelInfo.cpp +++ b/dom/fetch/ChannelInfo.cpp @@ -8,40 +8,58 @@ #include "nsCOMPtr.h" #include "nsIChannel.h" #include "nsIHttpChannel.h" #include "nsSerializationHelper.h" #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/ipc/ChannelInfo.h" #include "nsIJARChannel.h" #include "nsJARChannel.h" +#include "nsNetUtil.h" using namespace mozilla; using namespace mozilla::dom; void ChannelInfo::InitFromChannel(nsIChannel* aChannel) { + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); nsCOMPtr<nsISupports> securityInfo; aChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); if (securityInfo) { SetSecurityInfo(securityInfo); } + nsLoadFlags loadFlags = 0; + aChannel->GetLoadFlags(&loadFlags); + mRedirected = (loadFlags & nsIChannel::LOAD_REPLACE); + if (mRedirected) { + // Save the spec and not the nsIURI object itself, since those objects are + // not thread-safe, and releasing them somewhere other than the main thread + // is not possible. + nsCOMPtr<nsIURI> redirectedURI; + aChannel->GetURI(getter_AddRefs(redirectedURI)); + if (redirectedURI) { + redirectedURI->GetSpec(mRedirectedURISpec); + } + } + mInited = true; } void ChannelInfo::InitFromIPCChannelInfo(const ipc::IPCChannelInfo& aChannelInfo) { MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); mSecurityInfo = aChannelInfo.securityInfo(); + mRedirectedURISpec = aChannelInfo.redirectedURI(); + mRedirected = aChannelInfo.redirected(); mInited = true; } void ChannelInfo::SetSecurityInfo(nsISupports* aSecurityInfo) { MOZ_ASSERT(mSecurityInfo.IsEmpty(), "security info should only be set once"); @@ -51,53 +69,83 @@ ChannelInfo::SetSecurityInfo(nsISupports return; } NS_SerializeToString(serializable, mSecurityInfo); } nsresult ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) { + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mInited); + // These pointers may be null at this point. They must be checked before + // being dereferenced. + nsCOMPtr<nsIHttpChannel> httpChannel = + do_QueryInterface(aChannel); + nsCOMPtr<nsIJARChannel> jarChannel = + do_QueryInterface(aChannel); + if (!mSecurityInfo.IsEmpty()) { nsCOMPtr<nsISupports> infoObj; nsresult rv = NS_DeserializeObject(mSecurityInfo, getter_AddRefs(infoObj)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - nsCOMPtr<nsIHttpChannel> httpChannel = - do_QueryInterface(aChannel); if (httpChannel) { net::HttpBaseChannel* httpBaseChannel = static_cast<net::HttpBaseChannel*>(httpChannel.get()); rv = httpBaseChannel->OverrideSecurityInfo(infoObj); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { - nsCOMPtr<nsIJARChannel> jarChannel = - do_QueryInterface(aChannel); if (NS_WARN_IF(!jarChannel)) { return NS_ERROR_FAILURE; } static_cast<nsJARChannel*>(jarChannel.get())-> OverrideSecurityInfo(infoObj); } } + if (mRedirected) { + nsLoadFlags flags = 0; + aChannel->GetLoadFlags(&flags); + flags |= nsIChannel::LOAD_REPLACE; + aChannel->SetLoadFlags(flags); + + nsCOMPtr<nsIURI> redirectedURI; + nsresult rv = NS_NewURI(getter_AddRefs(redirectedURI), + mRedirectedURISpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (httpChannel) { + net::HttpBaseChannel* httpBaseChannel = + static_cast<net::HttpBaseChannel*>(httpChannel.get()); + httpBaseChannel->OverrideURI(redirectedURI); + } else { + if (NS_WARN_IF(!jarChannel)) { + return NS_ERROR_FAILURE; + } + static_cast<nsJARChannel*>(jarChannel.get())->OverrideURI(redirectedURI); + } + } + return NS_OK; } ipc::IPCChannelInfo ChannelInfo::AsIPCChannelInfo() const { // This may be called when mInited is false, for example if we try to store // a synthesized Response object into the Cache. Uninitialized and empty // ChannelInfo objects are indistinguishable at the IPC level, so this is // fine. IPCChannelInfo ipcInfo; ipcInfo.securityInfo() = mSecurityInfo; + ipcInfo.redirectedURI() = mRedirectedURISpec; + ipcInfo.redirected() = mRedirected; return ipcInfo; }
--- a/dom/fetch/ChannelInfo.h +++ b/dom/fetch/ChannelInfo.h @@ -3,18 +3,20 @@ /* 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/. */ #ifndef mozilla_dom_ChannelInfo_h #define mozilla_dom_ChannelInfo_h #include "nsString.h" +#include "nsCOMPtr.h" class nsIChannel; +class nsIURI; namespace mozilla { namespace ipc { class IPCChannelInfo; } // namespace ipc namespace dom { @@ -37,30 +39,35 @@ namespace dom { // initialized. There are assertions ensuring these invariants. class ChannelInfo final { public: typedef mozilla::ipc::IPCChannelInfo IPCChannelInfo; ChannelInfo() : mInited(false) + , mRedirected(false) { } ChannelInfo(const ChannelInfo& aRHS) : mSecurityInfo(aRHS.mSecurityInfo) + , mRedirectedURISpec(aRHS.mRedirectedURISpec) , mInited(aRHS.mInited) + , mRedirected(aRHS.mRedirected) { } ChannelInfo& operator=(const ChannelInfo& aRHS) { mSecurityInfo = aRHS.mSecurityInfo; + mRedirectedURISpec = aRHS.mRedirectedURISpec; mInited = aRHS.mInited; + mRedirected = aRHS.mRedirected; return *this; } void InitFromChannel(nsIChannel* aChannel); void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo); // This restores every possible information stored from a previous channel // object on a new one. @@ -73,15 +80,17 @@ public: IPCChannelInfo AsIPCChannelInfo() const; private: void SetSecurityInfo(nsISupports* aSecurityInfo); private: nsCString mSecurityInfo; + nsCString mRedirectedURISpec; bool mInited; + bool mRedirected; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_ChannelInfo_h
--- a/dom/fetch/ChannelInfo.ipdlh +++ b/dom/fetch/ChannelInfo.ipdlh @@ -3,12 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ namespace mozilla { namespace ipc { struct IPCChannelInfo { nsCString securityInfo; + nsCString redirectedURI; + bool redirected; }; } // namespace ipc } // namespace mozilla
--- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -80,30 +80,35 @@ namespace dom { // Calls LoadSelectedImage on host element unless it has been superseded or // canceled -- this is the synchronous section of "update the image data". // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data class ImageLoadTask : public nsRunnable { public: explicit ImageLoadTask(HTMLImageElement *aElement) : mElement(aElement) - {} + { + mDocument = aElement->OwnerDoc(); + mDocument->BlockOnload(); + } NS_IMETHOD Run() { if (mElement->mPendingImageLoadTask == this) { mElement->mPendingImageLoadTask = nullptr; mElement->LoadSelectedImage(true, true); } + mDocument->UnblockOnload(false); return NS_OK; } private: ~ImageLoadTask() {} nsRefPtr<HTMLImageElement> mElement; + nsCOMPtr<nsIDocument> mDocument; }; HTMLImageElement::HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) : nsGenericHTMLElement(aNodeInfo) , mForm(nullptr) { // We start out broken AddStatesSilently(NS_EVENT_STATE_BROKEN);
--- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -3564,22 +3564,32 @@ nsHTMLDocument::QueryCommandSupported(co { *_retval = QueryCommandSupported(commandID); return NS_OK; } bool nsHTMLDocument::QueryCommandSupported(const nsAString& commandID) { - // Gecko technically supports the paste command, but non-privileged content - // will be unable to call it. For that reason, we report that paste is - // not supported to this non-privileged content (as it effectively is). - bool restricted = commandID.LowerCaseEqualsLiteral("paste"); - if (restricted && !nsContentUtils::IsCallerChrome()) { - return false; + // Gecko technically supports all the clipboard commands including + // cut/copy/paste, but non-privileged content will be unable to call + // paste, and depending on the pref "dom.allow_cut_copy", cut and copy + // may also be disallowed to be called from non-privileged content. + // For that reason, we report the support status of corresponding + // command accordingly. + if (!nsContentUtils::IsCallerChrome()) { + if (commandID.LowerCaseEqualsLiteral("paste")) { + return false; + } + if (nsContentUtils::IsCutCopyRestricted()) { + if (commandID.LowerCaseEqualsLiteral("cut") || + commandID.LowerCaseEqualsLiteral("copy")) { + return false; + } + } } // commandID is supported if it can be converted to a Midas command nsAutoCString cmdToDispatch; return ConvertToMidasInternalCommand(commandID, cmdToDispatch); } /* DOMString queryCommandValue(in DOMString commandID); */
--- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -589,10 +589,9 @@ skip-if = buildapp == 'b2g' || e10s skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage support-files = file_bug871161-1.html file_bug871161-2.html [test_bug1013316.html] [test_hash_encoded.html] [test_bug1081037.html] [test_window_open_close.html] skip-if = buildapp == 'b2g' # bug 1129014 [test_img_complete.html] -[test_viewport_resize.html] -skip-if = os == 'win' || os == 'mac' # bug 1163911 \ No newline at end of file +[test_viewport_resize.html] \ No newline at end of file
--- a/dom/media/eme/MediaKeySession.h +++ b/dom/media/eme/MediaKeySession.h @@ -11,16 +11,17 @@ #include "mozilla/ErrorResult.h" #include "nsCycleCollectionParticipant.h" #include "mozilla/DOMEventTargetHelper.h" #include "nsCOMPtr.h" #include "mozilla/dom/TypedArray.h" #include "mozilla/Mutex.h" #include "mozilla/dom/Date.h" #include "mozilla/dom/Promise.h" +#include "mozilla/DetailedPromise.h" #include "mozilla/dom/MediaKeySessionBinding.h" #include "mozilla/dom/MediaKeysBinding.h" #include "mozilla/dom/MediaKeyMessageEventBinding.h" struct JSContext; namespace mozilla { namespace dom {
--- a/dom/tests/mochitest/general/mochitest.ini +++ b/dom/tests/mochitest/general/mochitest.ini @@ -99,8 +99,9 @@ skip-if = buildapp == 'mulet' || toolkit skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_windowedhistoryframes.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_navigation_timing.html] skip-if = buildapp == 'b2g' || buildapp == 'mulet' [test_bug1012662_editor.html] [test_bug1012662_noeditor.html] [test_bug1161721.html] +[test_bug1170911.html]
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug1170911.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1012662 +--> +<head> + <title>Test for Bug 1170911</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=1170911">Mozilla Bug 1170911</a> +<p id="display"></p> + +<div id="content"> + <textarea>textarea text</textarea> +</div> + +<pre id="test"> +<script> +const TEXTAREA = document.querySelector('textarea'); +const TEXTAREA_VALUE = TEXTAREA.value; + +function doTest() { + is(document.queryCommandSupported("copy"), false, + "Copy support should have been disabled"); + is(document.queryCommandSupported("cut"), false, + "Cut support should have been disabled"); + + document.addEventListener("keydown", tryCopy); + synthesizeKey("Q", {}); +} + +function tryCopy(evt) { + evt.preventDefault(); + document.removeEventListener("keydown", tryCopy); + TEXTAREA.setSelectionRange(0, TEXTAREA_VALUE.length); + TEXTAREA.focus(); + + SimpleTest.waitForClipboard(null, function () { + is(document.queryCommandEnabled("copy"), false, + "Copy should not be allowed when dom.allow_cut_copy is off"); + is(document.execCommand("copy"), false, + "Copy should not be executed when dom.allow_cut_copy is off"); + is(TEXTAREA.value, TEXTAREA_VALUE, + "Content in the textarea shouldn't be changed"); + TEXTAREA.value = TEXTAREA_VALUE; + }, + /* success fn */ SimpleTest.finish, + /* failure fn */ function () { + document.addEventListener("keydown", tryCut); + synthesizeKey("Q", {}); + }, + /* flavor */ undefined, + /* timeout */ undefined, + /* expect failure */ true); +} + +function tryCut(evt) { + evt.preventDefault(); + document.removeEventListener("keydown", tryCut); + TEXTAREA.setSelectionRange(0, TEXTAREA_VALUE.length); + TEXTAREA.focus(); + + SimpleTest.waitForClipboard(null, function () { + is(document.queryCommandEnabled("cut"), false, + "Cut should not be allowed when dom.allow_cut_copy is off"); + is(document.execCommand("cut"), false, + "Cut should not be executed when dom.allow_cut_copy is off"); + is(TEXTAREA.value, TEXTAREA_VALUE, + "Content in the textarea shouldn't be changed"); + }, + /* success fn */ SimpleTest.finish, + /* failure fn */ SimpleTest.finish, + /* flavor */ undefined, + /* timeout */ undefined, + /* expect failure */ true); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(() => { + SpecialPowers.pushPrefEnv({"set": [["dom.allow_cut_copy", false]]}, doTest); +}); + +</script> +</pre> +</body> +</html>
--- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -912,21 +912,21 @@ var interfaceNamesInGlobalScope = "Screen", // IMPORTANT: Do not change this list without review from a DOM peer! "ScriptProcessorNode", // IMPORTANT: Do not change this list without review from a DOM peer! "ScrollAreaEvent", // IMPORTANT: Do not change this list without review from a DOM peer! "Selection", // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "ServiceWorker", disabled: true, b2g: false}, + {name: "ServiceWorker", release: false, b2g: false}, // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "ServiceWorkerContainer", disabled: true, b2g: false}, + {name: "ServiceWorkerContainer", release: false, b2g: false}, // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "ServiceWorkerRegistration", disabled: true, b2g: false}, + {name: "ServiceWorkerRegistration", release: false, b2g: false}, // IMPORTANT: Do not change this list without review from a DOM peer! "SettingsLock", // IMPORTANT: Do not change this list without review from a DOM peer! "SettingsManager", // IMPORTANT: Do not change this list without review from a DOM peer! "ShadowRoot", // Bogus, but the test harness forces it on. See bug 1159768. // IMPORTANT: Do not change this list without review from a DOM peer! "SharedWorker",
index 4cc5b89a08ef418e4b76784a174041392570442f..b179b0819cc03684676645149018a195b8b8790a GIT binary patch literal 3362 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 Qz7Wd;7_vXP?F;XJ0p~_hr2qf`
--- a/dom/workers/test/serviceworkers/app-protocol/controlled.html +++ b/dom/workers/test/serviceworkers/app-protocol/controlled.html @@ -12,15 +12,19 @@ function runTests() { .then(() => { return testFetchAppResource('foo.txt', 'swresponse', 'text/plain'); }) .then(() => { return testFetchAppResource('test_custom_content_type', 'customContentType', 'text/html'); }) + .then(testRedirectedResponse) + .then(testRedirectedHttpsResponse) + .then(testCachedRedirectedResponse) + .then(testCachedRedirectedHttpsResponse) .then(done); } </script> </head> <body onload='runTests()'> </body> </html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/realindex.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +real index
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/realindex.html^headers^ @@ -0,0 +1,1 @@ +Access-Control-Allow-Origin: *
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/app-protocol/realindex.html", false); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/redirect.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/app-protocol/realindex.html", false); +}
--- a/dom/workers/test/serviceworkers/app-protocol/sw.js +++ b/dom/workers/test/serviceworkers/app-protocol/sw.js @@ -1,8 +1,25 @@ +const kHTTPRedirect = "http://example.com/tests/dom/workers/test/serviceworkers/app-protocol/redirect.sjs"; +const kHTTPSRedirect = "https://example.com/tests/dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs"; + +self.addEventListener('install', (event) => { + event.waitUntil( + self.caches.open("origin-app-cache") + .then(c => { + return Promise.all( + [ + c.add(kHTTPRedirect), + c.add(kHTTPSRedirect), + ] + ); + }) + ); +}); + self.addEventListener('fetch', (event) => { if (event.request.url.indexOf('foo.txt') >= 0) { event.respondWith(new Response('swresponse', { headers: {'Content-Type': 'text/plain'} })); } if (event.request.url.indexOf('test_doc_load_interception.js') >= 0 ) { @@ -12,9 +29,35 @@ self.addEventListener('fetch', (event) = })); } if (event.request.url.indexOf('test_custom_content_type') >= 0) { event.respondWith(new Response('customContentType', { headers: {'Content-Type': 'text/html'} })); } + + if (event.request.url.indexOf('redirected.html') >= 0) { + event.respondWith(fetch(kHTTPRedirect)); + } + + if (event.request.url.indexOf('redirected-https.html') >= 0) { + event.respondWith(fetch(kHTTPSRedirect)); + } + + if (event.request.url.indexOf('redirected-cached.html') >= 0) { + event.respondWith( + self.caches.open("origin-app-cache") + .then(c => { + return c.match(kHTTPRedirect); + }) + ); + } + + if (event.request.url.indexOf('redirected-https-cached.html') >= 0) { + event.respondWith( + self.caches.open("origin-app-cache") + .then(c => { + return c.match(kHTTPSRedirect); + }) + ); + } });
--- a/dom/workers/test/serviceworkers/app-protocol/test.js +++ b/dom/workers/test/serviceworkers/app-protocol/test.js @@ -32,8 +32,41 @@ function testFetchAppResource(aUrl, contentType + ' should match with ' + aExpectedContentType)); } return res.text().then(body => { ok(body == aExpectedResponse, 'body ' + body + ' should match with ' + aExpectedResponse); }); }); } + +function testRedirectedResponse() { + return testRedirectedResponseWorker("redirected", "IFRAMELOADED"); +} + +function testRedirectedHttpsResponse() { + return testRedirectedResponseWorker("redirected-https", "HTTPSIFRAMELOADED"); +} + +function testCachedRedirectedResponse() { + return testRedirectedResponseWorker("redirected-cached", "IFRAMELOADED"); +} + +function testCachedRedirectedHttpsResponse() { + return testRedirectedResponseWorker("redirected-https-cached", "HTTPSIFRAMELOADED"); +} + +function testRedirectedResponseWorker(aFrameId, aAlert) { + // Because of the CSP policies applied to privileged apps, we cannot run + // inline script inside realindex.html, and loading a script from the app:// + // URI is also not an option, so we let the parent iframe which has access + // to the SpecialPowers API use those privileges to access the document. + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.src = aFrameId + ".html"; + iframe.id = aFrameId; + return new Promise(resolve => { + iframe.addEventListener("load", event => { + alert(aAlert); + resolve(); + }, false); + }); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html", false); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js @@ -0,0 +1,23 @@ +var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/https/"; + +self.addEventListener("install", function(event) { + event.waitUntil( + self.caches.open("origin-cache") + .then(c => { + return c.add(prefix + 'index-https.sjs'); + }) + ); +}); + +self.addEventListener("fetch", function(event) { + if (event.request.url.indexOf("index-cached-https.sjs") >= 0) { + event.respondWith( + self.caches.open("origin-cache") + .then(c => { + return c.match(prefix + 'index-https.sjs'); + }) + ); + } else { + event.respondWith(fetch(event.request)); + } +});
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + window.opener.postMessage({status: "domain", data: document.domain}, "*"); + window.opener.postMessage({status: "origin", data: location.origin}, "*"); + window.opener.postMessage({status: "done"}, "*"); +</script>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ @@ -0,0 +1,1 @@ +Access-Control-Allow-Origin: https://example.com
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("origin_test.js", {scope: "."}); +</script>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/realindex.html", false); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/index.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/realindex.html", false); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js @@ -0,0 +1,35 @@ +var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/"; + +self.addEventListener("install", function(event) { + event.waitUntil( + self.caches.open("origin-cache") + .then(c => { + return Promise.all( + [ + c.add(prefix + 'index.sjs'), + c.add(prefix + 'index-to-https.sjs'), + ] + ); + }) + ); +}); + +self.addEventListener("fetch", function(event) { + if (event.request.url.indexOf("index-cached.sjs") >= 0) { + event.respondWith( + self.caches.open("origin-cache") + .then(c => { + return c.match(prefix + 'index.sjs'); + }) + ); + } else if (event.request.url.indexOf("index-to-https-cached.sjs") >= 0) { + event.respondWith( + self.caches.open("origin-cache") + .then(c => { + return c.match(prefix + 'index-to-https.sjs'); + }) + ); + } else { + event.respondWith(fetch(event.request)); + } +});
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + window.opener.postMessage({status: "domain", data: document.domain}, "*"); + window.opener.postMessage({status: "origin", data: location.origin}, "*"); + window.opener.postMessage({status: "done"}, "*"); +</script>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ @@ -0,0 +1,1 @@ +Access-Control-Allow-Origin: http://mochi.test:8888
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/register.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + function ok(v, msg) { + window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*"); + } + + function done(reg) { + ok(reg.active, "The active worker should be available."); + window.parent.postMessage({status: "registrationdone"}, "*"); + } + + navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.register("origin_test.js", {scope: "."}); +</script>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/unregister.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.getRegistration(".").then(function(registration) { + registration.unregister().then(function(success) { + if (success) { + window.parent.postMessage({status: "unregistrationdone"}, "*"); + } + }, function(e) { + dump("Unregistering the SW failed with " + e + "\n"); + }); + }); +</script>
--- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -42,16 +42,29 @@ support-files = fetch/https/index.html fetch/https/register.html fetch/https/unregister.html fetch/https/https_test.js fetch/https/clonedresponse/index.html fetch/https/clonedresponse/register.html fetch/https/clonedresponse/unregister.html fetch/https/clonedresponse/https_test.js + fetch/origin/index.sjs + fetch/origin/index-to-https.sjs + fetch/origin/realindex.html + fetch/origin/realindex.html^headers^ + fetch/origin/register.html + fetch/origin/unregister.html + fetch/origin/origin_test.js + fetch/origin/https/index-https.sjs + fetch/origin/https/realindex.html + fetch/origin/https/realindex.html^headers^ + fetch/origin/https/register.html + fetch/origin/https/unregister.html + fetch/origin/https/origin_test.js fetch/requesturl/index.html fetch/requesturl/redirect.sjs fetch/requesturl/redirector.html fetch/requesturl/register.html fetch/requesturl/requesturl_test.js fetch/requesturl/secret.html fetch/requesturl/unregister.html fetch/sandbox/index.html @@ -154,8 +167,14 @@ support-files = [test_sanitize_domain.html] [test_service_worker_allowed.html] [test_app_protocol.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_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]
--- a/dom/workers/test/serviceworkers/test_app_protocol.html +++ b/dom/workers/test/serviceworkers/test_app_protocol.html @@ -23,17 +23,18 @@ let gApp; function setup() { return new Promise((resolve, reject) => { SpecialPowers.setAllAppsLaunchable(true); SpecialPowers.pushPrefEnv({'set': [ ['dom.mozBrowserFramesEnabled', true], ['dom.serviceWorkers.exemptFromPerDomainMax', true], ['dom.serviceWorkers.enabled', true], - ['dom.serviceWorkers.testing.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 } ], () => { SpecialPowers.autoConfirmAppInstall(() => { SpecialPowers.autoConfirmAppUninstall(resolve); @@ -98,16 +99,28 @@ function loadControlled() { iframe.setAttribute('mozbrowser', 'true'); iframe.setAttribute('mozapp', gApp.manifestURL); iframe.addEventListener('mozbrowsershowmodalprompt', function listener(e) { let message = e.detail.message; if (/OK/.exec(message)) { ok(true, "Message from app: " + message); } else if (/KO/.exec(message)) { ok(false, "Message from app: " + message); + } else if (/HTTPSIFRAMELOADED/.exec(message)) { + let doc = SpecialPowers.wrap(iframe).contentDocument; + let innerDoc = SpecialPowers.wrap(doc.getElementById("redirected-https").contentDocument); + let innerLocation = innerDoc.defaultView.location; + is(innerDoc.domain, "example.org", "Correct domain expected (https)"); + is(innerLocation.origin, "https://example.org", "Correct origin expected (https)"); + } else if (/IFRAMELOADED/.exec(message)) { + let doc = SpecialPowers.wrap(iframe).contentDocument; + let innerDoc = SpecialPowers.wrap(doc.getElementById("redirected").contentDocument); + let innerLocation = innerDoc.defaultView.location; + is(innerDoc.domain, "example.org", "Correct domain expected"); + is(innerLocation.origin, "http://example.org", "Correct origin expected"); } else if (/DONE/.exec(message)) { ok(true, "Messaging from app complete"); iframe.removeEventListener('mozbrowsershowmodalprompt', listener); let domParent = document.getElementById('container'); domParent.removeChild(iframe); resolve(); } else { ok(false, "Unexpected message received: " + message);
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html @@ -0,0 +1,56 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the origin of a redirected response from a service worker</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"> +<iframe></iframe> +</div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var iframe; + function runTest() { + iframe = document.querySelector("iframe"); + iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/register.html"; + var win; + window.onmessage = function(e) { + if (e.data.status == "ok") { + ok(e.data.result, e.data.message); + } else if (e.data.status == "registrationdone") { + win = window.open("https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs", "mywindow", "width=100,height=100"); + } else if (e.data.status == "domain") { + is(e.data.data, "example.org", "Correct domain expected"); + } else if (e.data.status == "origin") { + is(e.data.data, "https://example.org", "Correct origin expected"); + } else if (e.data.status == "done") { + win.close(); + iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html"; + } else if (e.data.status == "unregistrationdone") { + window.onmessage = null; + ok(true, "Test finished successfully"); + SimpleTest.finish(); + } + }; + } + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.caches.enabled", true], + ]}, runTest); + }; +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html @@ -0,0 +1,56 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the origin of a redirected response from a service worker</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"> +<iframe></iframe> +</div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var iframe; + function runTest() { + iframe = document.querySelector("iframe"); + iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/register.html"; + var win; + window.onmessage = function(e) { + if (e.data.status == "ok") { + ok(e.data.result, e.data.message); + } else if (e.data.status == "registrationdone") { + win = window.open("https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/index-cached-https.sjs", "mywindow", "width=100,height=100"); + } else if (e.data.status == "domain") { + is(e.data.data, "example.org", "Correct domain expected"); + } else if (e.data.status == "origin") { + is(e.data.data, "https://example.org", "Correct origin expected"); + } else if (e.data.status == "done") { + win.close(); + iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html"; + } else if (e.data.status == "unregistrationdone") { + window.onmessage = null; + ok(true, "Test finished successfully"); + SimpleTest.finish(); + } + }; + } + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.caches.enabled", true], + ]}, runTest); + }; +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_origin_after_redirect.html @@ -0,0 +1,57 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the origin of a redirected response from a service worker</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"> +<iframe></iframe> +</div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var iframe; + function runTest() { + iframe = document.querySelector("iframe"); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html"; + var win; + window.onmessage = function(e) { + if (e.data.status == "ok") { + ok(e.data.result, e.data.message); + } else if (e.data.status == "registrationdone") { + win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index.sjs", "mywindow", "width=100,height=100"); + } else if (e.data.status == "domain") { + is(e.data.data, "example.org", "Correct domain expected"); + } else if (e.data.status == "origin") { + is(e.data.data, "http://example.org", "Correct origin expected"); + } else if (e.data.status == "done") { + win.close(); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html"; + } else if (e.data.status == "unregistrationdone") { + window.onmessage = null; + ok(true, "Test finished successfully"); + SimpleTest.finish(); + } + }; + } + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.caches.enabled", true], + ]}, runTest); + }; +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html @@ -0,0 +1,57 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the origin of a redirected response from a service worker</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"> +<iframe></iframe> +</div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var iframe; + function runTest() { + iframe = document.querySelector("iframe"); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html"; + var win; + window.onmessage = function(e) { + if (e.data.status == "ok") { + ok(e.data.result, e.data.message); + } else if (e.data.status == "registrationdone") { + win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index-cached.sjs", "mywindow", "width=100,height=100"); + } else if (e.data.status == "domain") { + is(e.data.data, "example.org", "Correct domain expected"); + } else if (e.data.status == "origin") { + is(e.data.data, "http://example.org", "Correct origin expected"); + } else if (e.data.status == "done") { + win.close(); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html"; + } else if (e.data.status == "unregistrationdone") { + window.onmessage = null; + ok(true, "Test finished successfully"); + SimpleTest.finish(); + } + }; + } + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.caches.enabled", true], + ]}, runTest); + }; +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html @@ -0,0 +1,57 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the origin of a redirected response from a service worker</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"> +<iframe></iframe> +</div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var iframe; + function runTest() { + iframe = document.querySelector("iframe"); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html"; + var win; + window.onmessage = function(e) { + if (e.data.status == "ok") { + ok(e.data.result, e.data.message); + } else if (e.data.status == "registrationdone") { + win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs", "mywindow", "width=100,height=100"); + } else if (e.data.status == "domain") { + is(e.data.data, "example.org", "Correct domain expected"); + } else if (e.data.status == "origin") { + is(e.data.data, "https://example.org", "Correct origin expected"); + } else if (e.data.status == "done") { + win.close(); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html"; + } else if (e.data.status == "unregistrationdone") { + window.onmessage = null; + ok(true, "Test finished successfully"); + SimpleTest.finish(); + } + }; + } + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.caches.enabled", true], + ]}, runTest); + }; +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html @@ -0,0 +1,57 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the origin of a redirected response from a service worker</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"> +<iframe></iframe> +</div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var iframe; + function runTest() { + iframe = document.querySelector("iframe"); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html"; + var win; + window.onmessage = function(e) { + if (e.data.status == "ok") { + ok(e.data.result, e.data.message); + } else if (e.data.status == "registrationdone") { + win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index-to-https-cached.sjs", "mywindow", "width=100,height=100"); + } else if (e.data.status == "domain") { + is(e.data.data, "example.org", "Correct domain expected"); + } else if (e.data.status == "origin") { + is(e.data.data, "https://example.org", "Correct origin expected"); + } else if (e.data.status == "done") { + win.close(); + iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html"; + } else if (e.data.status == "unregistrationdone") { + window.onmessage = null; + ok(true, "Test finished successfully"); + SimpleTest.finish(); + } + }; + } + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.caches.enabled", true], + ]}, runTest); + }; +</script> +</pre> +</body> +</html>
--- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -152,17 +152,17 @@ var interfaceNamesInGlobalScope = "PerformanceMeasure", // IMPORTANT: Do not change this list without review from a DOM peer! "Promise", // IMPORTANT: Do not change this list without review from a DOM peer! "Request", // IMPORTANT: Do not change this list without review from a DOM peer! "Response", // IMPORTANT: Do not change this list without review from a DOM peer! - { name: "ServiceWorkerRegistration", disabled: true, b2g: false }, + { name: "ServiceWorkerRegistration", release: false, b2g: false }, // IMPORTANT: Do not change this list without review from a DOM peer! "TextDecoder", // IMPORTANT: Do not change this list without review from a DOM peer! "TextEncoder", // IMPORTANT: Do not change this list without review from a DOM peer! "XMLHttpRequest", // IMPORTANT: Do not change this list without review from a DOM peer! "XMLHttpRequestEventTarget",
--- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -2241,17 +2241,17 @@ gfxPlatform::OptimalFormatForContent(gfx * There are a number of layers acceleration (or layers in general) preferences * that should be consistent for the lifetime of the application (bug 840967). * As such, we will evaluate them all as soon as one of them is evaluated * and remember the values. Changing these preferences during the run will * not have any effect until we restart. */ static bool sLayersSupportsD3D9 = false; static bool sLayersSupportsD3D11 = false; -static bool sANGLESupportsD3D11 = false; +bool gANGLESupportsD3D11 = false; static bool sLayersSupportsHardwareVideoDecoding = false; static bool sLayersHardwareVideoDecodingFailed = false; static bool sBufferRotationCheckPref = true; static bool sPrefBrowserTabsRemoteAutostart = false; static bool sLayersAccelerationPrefsInitialized = false; void @@ -2286,17 +2286,17 @@ InitLayersAccelerationPrefs() } } if (!gfxPrefs::LayersD3D11DisableWARP()) { // Always support D3D11 when WARP is allowed. sLayersSupportsD3D11 = true; } if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - sANGLESupportsD3D11 = true; + gANGLESupportsD3D11 = true; } } } #endif if (Preferences::GetBool("media.hardware-video-decoding.enabled", false) && #ifdef XP_WIN Preferences::GetBool("media.windows-media-foundation.use-dxva", true) && @@ -2342,17 +2342,17 @@ gfxPlatform::CanUseHardwareVideoDecoding MOZ_ASSERT(sLayersAccelerationPrefsInitialized); return sLayersSupportsHardwareVideoDecoding && !sLayersHardwareVideoDecodingFailed; } bool gfxPlatform::CanUseDirect3D11ANGLE() { MOZ_ASSERT(sLayersAccelerationPrefsInitialized); - return sANGLESupportsD3D11; + return gANGLESupportsD3D11; } bool gfxPlatform::BufferRotationEnabled() { MutexAutoLock autoLock(*gGfxPlatformPrefsLock);
--- a/gfx/thebes/gfxPlatform.h +++ b/gfx/thebes/gfxPlatform.h @@ -126,16 +126,18 @@ enum eGfxLog { eGfxLog_cmapdata = 4, // text perf data eGfxLog_textperf = 5 }; // when searching through pref langs, max number of pref langs const uint32_t kMaxLenPrefLangList = 32; +extern bool gANGLESupportsD3D11; + #define UNINITIALIZED_VALUE (-1) inline const char* GetBackendName(mozilla::gfx::BackendType aBackend) { switch (aBackend) { case mozilla::gfx::BackendType::DIRECT2D: return "direct2d";
--- a/gfx/thebes/gfxPlatformMac.cpp +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -572,17 +572,22 @@ static CVReturn VsyncCallback(CVDisplayL // Executed on OS X hardware vsync thread OSXVsyncSource::OSXDisplay* display = (OSXVsyncSource::OSXDisplay*) aDisplayLinkContext; int64_t nextVsyncTimestamp = aOutputTime->hostTime; mozilla::TimeStamp nextVsync = mozilla::TimeStamp::FromSystemTime(nextVsyncTimestamp); mozilla::TimeStamp previousVsync = display->mPreviousTimestamp; display->mPreviousTimestamp = nextVsync; mozilla::TimeStamp now = TimeStamp::Now(); - MOZ_ASSERT(nextVsync > previousVsync); + if (nextVsync <= previousVsync) { + TimeDuration next = nextVsync - now; + TimeDuration prev = now - previousVsync; + printf_stderr("Next from now: %f, prev from now: %f\n", next.ToMilliseconds(), prev.ToMilliseconds()); + MOZ_ASSERT(false, "Next vsync less than previous vsync\n"); + } // Bug 1158321 - The VsyncCallback can sometimes execute before the reported // vsync time. In those cases, normalize the timestamp to Now() as sending // timestamps in the future has undefined behavior. See the comment above // OSXDisplay::mPreviousTimestamp if (now < previousVsync) { previousVsync = now; }
--- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -1713,22 +1713,24 @@ bool DoesD3D11DeviceWork(ID3D11Device *d if (GetModuleHandleW(L"dlumd32.dll") && GetModuleHandleW(L"igd10umd32.dll")) { nsString displayLinkModuleVersionString; gfxWindowsPlatform::GetDLLVersion(L"dlumd32.dll", displayLinkModuleVersionString); uint64_t displayLinkModuleVersion; if (!ParseDriverVersion(displayLinkModuleVersionString, &displayLinkModuleVersion)) { #if defined(MOZ_CRASHREPORTER) CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DisplayLink: could not parse version\n")); #endif + gANGLESupportsD3D11 = false; return false; } if (displayLinkModuleVersion <= V(8,6,1,36484)) { #if defined(MOZ_CRASHREPORTER) CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DisplayLink: too old version\n")); #endif + gANGLESupportsD3D11 = false; return false; } } result = true; return true; } // See bug 1083071. On some drivers, Direct3D 11 CreateShaderResourceView fails
--- a/intl/uconv/tests/unit/test_bug399257.js +++ b/intl/uconv/tests/unit/test_bug399257.js @@ -1,68 +1,66 @@ // Tests encoding of characters below U+0020 load('CharsetConversionTests.js'); const inString = "Hello\u000aWorld"; const expectedString = "Hello\nWorld"; function run_test() { var failures = false; + var encodingConverter = CreateScriptableConverter(); - var decoders = [ + var encoders = [ "Big5", "Big5-HKSCS", "EUC-JP", "EUC-KR", "gb18030", + "gbk", "IBM866", "ISO-2022-JP", + "ISO-8859-1", + "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", "ISO-8859-5", "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-8-I", "ISO-8859-10", "ISO-8859-13", "ISO-8859-14", "ISO-8859-15", "ISO-8859-16", - "ISO-8859-2", "KOI8-R", "KOI8-U", "Shift_JIS", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256", "windows-1257", "windows-1258", "windows-874", + "macintosh", "x-mac-cyrillic", + "x-user-defined", "UTF-8" ]; var counter = 0; - while (counter < decoders.length) { - ++counter; - var charset = decoders[counter]; - + while (counter < encoders.length) { + var charset = encoders[counter++]; dump("testing " + counter + " " + charset + "\n"); - try { - encodingConverter.charset = charset; - } catch(e) { - dump("Warning: couldn't set encoder charset to " + charset + "\n"); - continue; - } + encodingConverter.charset = charset; var codepageString = encodingConverter.ConvertFromUnicode(inString) + encodingConverter.Finish(); if (codepageString != expectedString) { dump(charset + " encoding failed\n"); for (var i = 0; i < expectedString.length; ++i) { if (codepageString.charAt(i) != expectedString.charAt(i)) { dump(i.toString(16) + ": 0x" + codepageString.charCodeAt(i).toString(16) + " != " +
--- a/intl/uconv/tests/unit/test_bug601429.js +++ b/intl/uconv/tests/unit/test_bug601429.js @@ -1,65 +1,63 @@ // Tests whether characters above 0x7F decode to ASCII characters liable to // expose XSS vulnerabilities load('CharsetConversionTests.js'); function run_test() { var failures = false; + var decodingConverter = CreateScriptableConverter(); var decoders = [ "Big5", "Big5-HKSCS", "EUC-JP", "EUC-KR", "gb18030", "IBM866", "ISO-2022-JP", + "ISO-8859-1", + "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", "ISO-8859-5", "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-8-I", "ISO-8859-10", "ISO-8859-13", "ISO-8859-14", "ISO-8859-15", "ISO-8859-16", - "ISO-8859-2", "KOI8-R", "KOI8-U", "Shift_JIS", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256", "windows-1257", "windows-1258", "windows-874", + "macintosh", "x-mac-cyrillic", + "x-user-defined", "UTF-8" ]; var counter = 0; while (counter < decoders.length) { - ++counter; - var charset = decoders[counter]; + var charset = decoders[counter++]; dump("testing " + counter + " " + charset + "\n"); - - try { - decodingConverter.charset = charset; - } catch(e) { - dump("Warning: couldn't set decoder charset to " + charset + "\n"); - continue; - } + + decodingConverter.charset = charset; for (var i = 0x80; i < 0x100; ++i) { var inString = String.fromCharCode(i); var outString; try { outString = decodingConverter.ConvertToUnicode(inString) + decodingConverter.Finish(); } catch(e) { outString = String.fromCharCode(0xFFFD);
--- a/intl/uconv/tests/unit/test_encode_gbk.js +++ b/intl/uconv/tests/unit/test_encode_gbk.js @@ -1,15 +1,15 @@ // Tests conversion from Unicode to gbk // This is a sniff test which doesn't cover the full gbk range: the test string // includes only the ASCII range and the first 63 double byte characters load('CharsetConversionTests.js'); const inString = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u20AC\u4E02\u4E04\u4E05\u4E06\u4E0F\u4E12\u4E17\u4E1F\u4E20\u4E21\u4E23\u4E26\u4E29\u4E2E\u4E2F\u4E31\u4E33\u4E35\u4E37\u4E3C\u4E40\u4E41\u4E42\u4E44\u4E46\u4E4A\u4E51\u4E55\u4E57\u4E5A\u4E5B\u4E62\u4E63\u4E64\u4E65\u4E67\u4E68\u4E6A\u4E6B\u4E6C\u4E6D\u4E6E\u4E6F\u4E72\u4E74\u4E75\u4E76\u4E77\u4E78\u4E79\u4E7A\u4E7B\u4E7C\u4E7D\u4E7F\u4E80\u4E81\u4E82\u4E83\u4E84\u4E85\u4E87\u4E8A\uFFFD"; -const expectedString = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81@\x81A\x81B\x81C\x81D\x81E\x81F\x81G\x81H\x81I\x81J\x81K\x81L\x81M\x81N\x81O\x81P\x81Q\x81R\x81S\x81T\x81U\x81V\x81W\x81X\x81Y\x81Z\x81[\x81\\\x81]\x81^\x81_\x81`\x81a\x81b\x81c\x81d\x81e\x81f\x81g\x81h\x81i\x81j\x81k\x81l\x81m\x81n\x81o\x81p\x81q\x81r\x81s\x81t\x81u\x81v\x81w\x81x\x81y\x81z\x81{\x81|\x81}\x81~"; +const expectedString = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81@\x81A\x81B\x81C\x81D\x81E\x81F\x81G\x81H\x81I\x81J\x81K\x81L\x81M\x81N\x81O\x81P\x81Q\x81R\x81S\x81T\x81U\x81V\x81W\x81X\x81Y\x81Z\x81[\x81\\\x81]\x81^\x81_\x81`\x81a\x81b\x81c\x81d\x81e\x81f\x81g\x81h\x81i\x81j\x81k\x81l\x81m\x81n\x81o\x81p\x81q\x81r\x81s\x81t\x81u\x81v\x81w\x81x\x81y\x81z\x81{\x81|\x81}\x81~?"; const aliases = [ "gbk", "x-gbk" ]; function run_test() { testEncodeAliases(); }
new file mode 100644 --- /dev/null +++ b/intl/uconv/tests/unit/test_unmapped.js @@ -0,0 +1,77 @@ +// Tests encoding of unmapped characters +load('CharsetConversionTests.js'); + +const inString = "\u2764"; +const expectedString = "?"; + +function run_test() { + var failures = false; + var encodingConverter = CreateScriptableConverter(); + + // this list excludes codepages that can represent all Unicode + var encoders = [ + "Big5", + "Big5-HKSCS", + "EUC-JP", + "EUC-KR", + "gbk", + "IBM866", + "ISO-2022-JP", + "ISO-8859-3", + "ISO-8859-4", + "ISO-8859-5", + "ISO-8859-6", + "ISO-8859-7", + "ISO-8859-8", + "ISO-8859-8-I", + "ISO-8859-10", + "ISO-8859-13", + "ISO-8859-14", + "ISO-8859-15", + "ISO-8859-16", + "ISO-8859-2", + "KOI8-R", + "KOI8-U", + "Shift_JIS", + "windows-1250", + "windows-1251", + "windows-1252", + "windows-1253", + "windows-1254", + "windows-1255", + "windows-1256", + "windows-1257", + "windows-1258", + "windows-874", + "x-mac-cyrillic" + ]; + + var counter = 0; + while (counter < encoders.length) { + var charset = encoders[counter++]; + + dump("testing " + counter + " " + charset + "\n"); + encodingConverter.charset = charset; + var codepageString = encodingConverter.ConvertFromUnicode(inString) + + encodingConverter.Finish(); + if (codepageString != expectedString) { + dump(charset + " encoding failed\n"); + for (var i = 0; i < expectedString.length; ++i) { + if (i >= codepageString.length) { + dump("output length " + codepageString.length + + " less than expected length " + expectedString.length + "\n"); + break; + } + if (codepageString.charAt(i) != expectedString.charAt(i)) { + dump(i.toString(16) + ": 0x" + + codepageString.charCodeAt(i).toString(16) + " != " + + expectedString.charCodeAt(i).toString(16) + "\n"); + } + } + failures = true; + } + } + if (failures) { + do_throw("test failed\n"); + } +}
--- a/intl/uconv/tests/unit/xpcshell.ini +++ b/intl/uconv/tests/unit/xpcshell.ini @@ -115,8 +115,9 @@ support-files = [test_encode_x_mac_hebrew.js] [test_encode_x_mac_icelandic.js] [test_encode_macintosh.js] [test_encode_x_mac_romanian.js] [test_encode_x_mac_turkish.js] [test_utf8_illegals.js] [test_input_stream.js] [test_bug1008832.js] +[test_unmapped.js]
--- a/intl/uconv/ucvcn/nsUnicodeToGBK.cpp +++ b/intl/uconv/ucvcn/nsUnicodeToGBK.cpp @@ -1,9 +1,9 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ /** * A character set converter from Unicode to GBK. * * * @created 08/Sept/1999 @@ -74,40 +74,45 @@ void nsUnicodeToGB18030::CreateExtension { mExtensionEncoder = new nsUnicodeToGB18030Uniq2Bytes(); } void nsUnicodeToGB18030::Create4BytesEncoder() { m4BytesEncoder = new nsUnicodeTo4BytesGB18030(); } -bool nsUnicodeToGB18030::EncodeSurrogate( - char16_t aSurrogateHigh, - char16_t aSurrogateLow, - char* aOut) +nsresult nsUnicodeToGB18030::EncodeSurrogate(char16_t aSurrogateHigh, + char16_t aSurrogateLow, + char* aOut, + int32_t aDestLength, + int32_t aBufferLength) { - if( NS_IS_HIGH_SURROGATE(aSurrogateHigh) && + if( NS_IS_HIGH_SURROGATE(aSurrogateHigh) && NS_IS_LOW_SURROGATE(aSurrogateLow) ) { - // notice that idx does not include the 0x10000 + // notice that idx does not include the 0x10000 uint32_t idx = ((aSurrogateHigh - (char16_t)0xD800) << 10 ) | (aSurrogateLow - (char16_t) 0xDC00); + if (aDestLength + 4 > aBufferLength) { + return NS_OK_UENC_MOREOUTPUT; + } + unsigned char *out = (unsigned char*) aOut; - // notice this is from 0x90 for supplment planes - out[0] = (idx / (10*126*10)) + 0x90; + // notice this is from 0x90 for supplementary planes + out[0] = (idx / (10*126*10)) + 0x90; idx %= (10*126*10); out[1] = (idx / (10*126)) + 0x30; idx %= (10*126); out[2] = (idx / (10)) + 0x81; out[3] = (idx % 10) + 0x30; - return true; - } - return false; -} + return NS_OK; + } + return NS_ERROR_UENC_NOMAPPING; +} //---------------------------------------------------------------------- // Class nsUnicodeToGBK [implementation] nsUnicodeToGBK::nsUnicodeToGBK(uint32_t aMaxLength) : nsEncoderSupport(aMaxLength) { mExtensionEncoder = nullptr; @@ -117,80 +122,76 @@ nsUnicodeToGBK::nsUnicodeToGBK(uint32_t void nsUnicodeToGBK::CreateExtensionEncoder() { mExtensionEncoder = new nsUnicodeToGBKUniq(); } void nsUnicodeToGBK::Create4BytesEncoder() { m4BytesEncoder = nullptr; } -bool nsUnicodeToGBK::TryExtensionEncoder( + +nsresult nsUnicodeToGBK::TryExtensionEncoder(char16_t aChar, + char* aOut, + int32_t *aOutLen) +{ + if( NS_IS_HIGH_SURROGATE(aChar) || + NS_IS_LOW_SURROGATE(aChar) ) + { + // performance tune for surrogate characters + return NS_ERROR_UENC_NOMAPPING; + } + if(! mExtensionEncoder ) + CreateExtensionEncoder(); + if(mExtensionEncoder) + { + int32_t len = 1; + return mExtensionEncoder->Convert(&aChar, &len, aOut, aOutLen); + } + return NS_ERROR_UENC_NOMAPPING; +} + +nsresult nsUnicodeToGBK::Try4BytesEncoder( char16_t aChar, char* aOut, int32_t *aOutLen ) { - if( NS_IS_HIGH_SURROGATE(aChar) || + if( NS_IS_HIGH_SURROGATE(aChar) || NS_IS_LOW_SURROGATE(aChar) ) { // performance tune for surrogate characters - return false; - } - if(! mExtensionEncoder ) - CreateExtensionEncoder(); - if(mExtensionEncoder) - { - int32_t len = 1; - nsresult res = NS_OK; - res = mExtensionEncoder->Convert(&aChar, &len, aOut, aOutLen); - if(NS_SUCCEEDED(res) && (*aOutLen > 0)) - return true; - } - return false; -} - -bool nsUnicodeToGBK::Try4BytesEncoder( - char16_t aChar, - char* aOut, - int32_t *aOutLen -) -{ - if( NS_IS_HIGH_SURROGATE(aChar) || - NS_IS_LOW_SURROGATE(aChar) ) - { - // performance tune for surrogate characters - return false; + return NS_ERROR_UENC_NOMAPPING; } if(! m4BytesEncoder ) Create4BytesEncoder(); - if(m4BytesEncoder) + if(m4BytesEncoder) { int32_t len = 1; nsresult res = NS_OK; res = m4BytesEncoder->Convert(&aChar, &len, aOut, aOutLen); NS_ASSERTION(NS_FAILED(res) || ((1 == len) && (4 == *aOutLen)), "unexpect conversion length"); - if(NS_SUCCEEDED(res) && (*aOutLen > 0)) - return true; + return res; } - return false; + return NS_ERROR_UENC_NOMAPPING; } -bool nsUnicodeToGBK::EncodeSurrogate( - char16_t aSurrogateHigh, - char16_t aSurrogateLow, - char* aOut) + +nsresult nsUnicodeToGBK::EncodeSurrogate(char16_t aSurrogateHigh, + char16_t aSurrogateLow, + char* aOut, + int32_t aDestLength, + int32_t aBufferLength) { - return false; // GBK cannot encode Surrogate, let the subclass encode it. -} + return NS_ERROR_UENC_NOMAPPING; // GBK cannot encode Surrogate, let the subclass encode it. +} -NS_IMETHODIMP nsUnicodeToGBK::ConvertNoBuff( - const char16_t * aSrc, - int32_t * aSrcLength, - char * aDest, - int32_t * aDestLength) +NS_IMETHODIMP nsUnicodeToGBK::ConvertNoBuffNoErr(const char16_t * aSrc, + int32_t * aSrcLength, + char * aDest, + int32_t * aDestLength) { int32_t iSrcLength = 0; int32_t iDestLength = 0; char16_t unicode; nsresult res = NS_OK; while (iSrcLength < *aSrcLength ) { unicode = *aSrc; @@ -212,94 +213,92 @@ NS_IMETHODIMP nsUnicodeToGBK::ConvertNoB break; } aDest[0] = byte1; aDest[1] = byte2; aDest += 2; // increment 2 bytes iDestLength +=2; } else { int32_t aOutLen = 2; - // make sure we still have 2 bytes for output first - if(iDestLength+2 > *aDestLength) - { - res = NS_OK_UENC_MOREOUTPUT; - break; - } // we cannot map in the common mapping. Let's try to // call the delegated 2 byte converter for the gbk or gb18030 // unique 2 byte mapping - if(TryExtensionEncoder(unicode, aDest, &aOutLen)) - { + res = TryExtensionEncoder(unicode, aDest, &aOutLen); + if (res == NS_OK) { iDestLength += aOutLen; aDest += aOutLen; + } else if (res == NS_OK_UENC_MOREOUTPUT) { + break; } else { - // make sure we still have 4 bytes for output first - if(iDestLength+4 > *aDestLength) - { - res = NS_OK_UENC_MOREOUTPUT; - break; - } // we still cannot map. Let's try to - // call the delegated GB18030 4 byte converter + // call the delegated GB18030 4 byte converter aOutLen = 4; if( NS_IS_HIGH_SURROGATE(unicode) ) { if((iSrcLength+1) < *aSrcLength ) { - if(EncodeSurrogate(aSrc[0],aSrc[1], aDest)) { + res = EncodeSurrogate(aSrc[0],aSrc[1], aDest, + iDestLength, *aDestLength); + if (res == NS_OK) { // since we got a surrogate pair, we need to increment src. - iSrcLength++ ; + iSrcLength++ ; aSrc++; iDestLength += aOutLen; aDest += aOutLen; } else { - // only get a high surrogate, but not a low surrogate - res = NS_ERROR_UENC_NOMAPPING; - iSrcLength++; // include length of the unmapped character + if (res == NS_ERROR_UENC_NOMAPPING) { + // only get a high surrogate, but not a low surrogate + iSrcLength++; // include length of the unmapped character + } break; } } else { mSurrogateHigh = aSrc[0]; + res = NS_OK; break; // this will go to afterwhileloop } } else { if( NS_IS_LOW_SURROGATE(unicode) ) { if(NS_IS_HIGH_SURROGATE(mSurrogateHigh)) { - if(EncodeSurrogate(mSurrogateHigh, aSrc[0], aDest)) { + res = EncodeSurrogate(mSurrogateHigh, aSrc[0], aDest, + iDestLength, *aDestLength); + if (res == NS_OK) { iDestLength += aOutLen; aDest += aOutLen; } else { - // only get a high surrogate, but not a low surrogate - res = NS_ERROR_UENC_NOMAPPING; - iSrcLength++; // include length of the unmapped character + if (res == NS_ERROR_UENC_NOMAPPING) { + // only get a high surrogate, but not a low surrogate + iSrcLength++; // include length of the unmapped character + } break; } } else { // only get a low surrogate, but not a low surrogate res = NS_ERROR_UENC_NOMAPPING; iSrcLength++; // include length of the unmapped character break; } } else { - if(Try4BytesEncoder(unicode, aDest, &aOutLen)) - { + res = Try4BytesEncoder(unicode, aDest, &aOutLen); + if (res == NS_OK) { NS_ASSERTION((aOutLen == 4), "we should always generate 4 bytes here"); iDestLength += aOutLen; aDest += aOutLen; } else { - res = NS_ERROR_UENC_NOMAPPING; - iSrcLength++; // include length of the unmapped character + if (res == NS_ERROR_UENC_NOMAPPING) { + iSrcLength++; // include length of the unmapped character + } break; } } } } - } + } } - iSrcLength++ ; // Each unicode char just count as one in char16_t string; + iSrcLength++ ; // Each unicode char just count as one in char16_t string; mSurrogateHigh = 0; aSrc++; if ( iDestLength >= (*aDestLength) && (iSrcLength < *aSrcLength) ) { res = NS_OK_UENC_MOREOUTPUT; break; } }
--- a/intl/uconv/ucvcn/nsUnicodeToGBK.h +++ b/intl/uconv/ucvcn/nsUnicodeToGBK.h @@ -1,9 +1,9 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ /** * A character set converter from Unicode to GBK. * * @@ -30,45 +30,43 @@ public: */ explicit nsUnicodeToGBK(uint32_t aMaxLengthFactor = 2); virtual ~nsUnicodeToGBK() {} protected: //-------------------------------------------------------------------- // Subclassing of nsEncoderSupport class [declaration] - NS_IMETHOD ConvertNoBuff(const char16_t * aSrc, - int32_t * aSrcLength, - char * aDest, - int32_t * aDestLength); - - NS_IMETHOD ConvertNoBuffNoErr(const char16_t * aSrc, int32_t * aSrcLength, - char * aDest, int32_t * aDestLength) - { - return NS_OK; - } // just make it not abstract; + NS_IMETHOD ConvertNoBuffNoErr(const char16_t * aSrc, + int32_t * aSrcLength, + char * aDest, + int32_t * aDestLength); virtual void CreateExtensionEncoder(); virtual void Create4BytesEncoder(); nsCOMPtr<nsIUnicodeEncoder> mExtensionEncoder; nsCOMPtr<nsIUnicodeEncoder> m4BytesEncoder; protected: char16_t mSurrogateHigh; nsGBKConvUtil mUtil; - bool TryExtensionEncoder(char16_t aChar, char* aDest, int32_t* aOutLen); - bool Try4BytesEncoder(char16_t aChar, char* aDest, int32_t* aOutLen); - virtual bool EncodeSurrogate(char16_t aSurrogateHigh, char16_t aSurrogateLow, char* aDest); + nsresult TryExtensionEncoder(char16_t aChar, char* aDest, int32_t* aOutLen); + nsresult Try4BytesEncoder(char16_t aChar, char* aDest, int32_t* aOutLen); + virtual nsresult EncodeSurrogate(char16_t aSurrogateHigh, + char16_t aSurrogateLow, char* aDest, + int32_t aDestLength, int32_t aBufferLength); }; class nsUnicodeToGB18030: public nsUnicodeToGBK { public: nsUnicodeToGB18030() : nsUnicodeToGBK(4) {} virtual ~nsUnicodeToGB18030() {} protected: virtual void CreateExtensionEncoder(); virtual void Create4BytesEncoder(); - virtual bool EncodeSurrogate(char16_t aSurrogateHigh, char16_t aSurrogateLow, char* aDest); + virtual nsresult EncodeSurrogate(char16_t aSurrogateHigh, + char16_t aSurrogateLow, char* aDest, + int32_t aDestLength, int32_t aBufferLength); }; #endif /* nsUnicodeToGBK_h___ */
deleted file mode 100644 --- a/ipc/chromium/src/base/third_party/purify/pure.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Header file of Pure API function declarations. - * -* (C) Copyright IBM Corporation. 2006, 2006. All Rights Reserved. - * You may recompile and redistribute these definitions as required. - * - * Version 1.0 - */ - -#if defined(PURIFY) || defined(QUANTIFY) - -#if defined(c_plusplus) || defined(__cplusplus) -extern "C" { -#endif - -// Don't include this file directly, use purify.h instead. -// If you need something that's not there, add it. -#ifdef PURIFY_PRIVATE_INCLUDE - -#define PURE_H_VERSION 1 -#include <stddef.h> - -////////////////////////////// -// API's Specific to Purify // -////////////////////////////// - -// TRUE when Purify is running. -int __cdecl PurifyIsRunning(void) ; -// -// Print a string to the viewer. -// -int __cdecl PurePrintf(const char *fmt, ...) ; -int __cdecl PurifyPrintf(const char *fmt, ...) ; -// -// Purify functions for leak and memory-in-use functionalty. -// -size_t __cdecl PurifyNewInuse(void) ; -size_t __cdecl PurifyAllInuse(void) ; -size_t __cdecl PurifyClearInuse(void) ; -size_t __cdecl PurifyNewLeaks(void) ; -size_t __cdecl PurifyAllLeaks(void) ; -size_t __cdecl PurifyClearLeaks(void) ; -// -// Purify functions for handle leakage. -// -size_t __cdecl PurifyAllHandlesInuse(void) ; -size_t __cdecl PurifyNewHandlesInuse(void) ; -// -// Functions that tell you about the state of memory. -// -size_t __cdecl PurifyDescribe(void *addr) ; -size_t __cdecl PurifyWhatColors(void *addr, size_t size) ; -// -// Functions to test the state of memory. If the memory is not -// accessable, an error is signaled just as if there were a memory -// reference and the function returns false. -// -int __cdecl PurifyAssertIsReadable(const void *addr, size_t size) ; // size used to be an int, until IA64 came along -int __cdecl PurifyAssertIsWritable(const void *addr, size_t size) ; -// -// Functions to test the state of memory. If the memory is not -// accessable, these functions return false. No error is signaled. -// -int __cdecl PurifyIsReadable(const void *addr, size_t size) ; -int __cdecl PurifyIsWritable(const void *addr, size_t size) ; -int __cdecl PurifyIsInitialized(const void *addr, size_t size) ; -// -// Functions to set the state of memory. -// -void __cdecl PurifyMarkAsInitialized(void *addr, size_t size) ; -void __cdecl PurifyMarkAsUninitialized(void *addr, size_t size) ; -// -// Functions to do late detection of ABWs, FMWs, IPWs. -// -#define PURIFY_HEAP_CRT (HANDLE) ~(__int64) 1 /* 0xfffffffe */ -#define PURIFY_HEAP_ALL (HANDLE) ~(__int64) 2 /* 0xfffffffd */ -#define PURIFY_HEAP_BLOCKS_LIVE 0x80000000 -#define PURIFY_HEAP_BLOCKS_DEFERRED_FREE 0x40000000 -#define PURIFY_HEAP_BLOCKS_ALL (PURIFY_HEAP_BLOCKS_LIVE|PURIFY_HEAP_BLOCKS_DEFERRED_FREE) -int __cdecl PurifyHeapValidate(unsigned int hHeap, unsigned int dwFlags, const void *addr) ; -int __cdecl PurifySetLateDetectScanCounter(int counter); -int __cdecl PurifySetLateDetectScanInterval(int seconds); -// -// Functions to support pool allocators -// -void __cdecl PurifySetPoolId(const void *mem, int id); -int __cdecl PurifyGetPoolId(const void *mem); -void __cdecl PurifySetUserData(const void *mem, void *data); -void * __cdecl PurifyGetUserData(const void *mem); -void __cdecl PurifyMapPool(int id, void(*fn)()); - - -//////////////////////////////// -// API's Specific to Quantify // -//////////////////////////////// - -// TRUE when Quantify is running. -int __cdecl QuantifyIsRunning(void) ; - -// -// Functions for controlling collection -// -int __cdecl QuantifyDisableRecordingData(void) ; -int __cdecl QuantifyStartRecordingData(void) ; -int __cdecl QuantifyStopRecordingData(void) ; -int __cdecl QuantifyClearData(void) ; -int __cdecl QuantifyIsRecordingData(void) ; - -// Add a comment to the dataset -int __cdecl QuantifyAddAnnotation(char *) ; - -// Save the current data, creating a "checkpoint" dataset -int __cdecl QuantifySaveData(void) ; - -// Set the name of the current thread in the viewer -int __cdecl QuantifySetThreadName(char *) ; - -//////////////////////////////// -// API's Specific to Coverage // -//////////////////////////////// - -// TRUE when Coverage is running. -int __cdecl CoverageIsRunning(void) ; -// -// Functions for controlling collection -// -int __cdecl CoverageDisableRecordingData(void) ; -int __cdecl CoverageStartRecordingData(void) ; -int __cdecl CoverageStopRecordingData(void) ; -int __cdecl CoverageClearData(void) ; -int __cdecl CoverageIsRecordingData(void) ; -// Add a comment to the dataset -int __cdecl CoverageAddAnnotation(char *) ; - -// Save the current data, creating a "checkpoint" dataset -int __cdecl CoverageSaveData(void) ; - - -#endif // PURIFY_PRIVATE_INCLUDE - -#if defined(c_plusplus) || defined(__cplusplus) -} -#endif - -#endif // defined(PURIFY) || defined(QUANTIFY) \ No newline at end of file
deleted file mode 100644 --- a/ipc/chromium/src/base/third_party/purify/pure_api.c +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Header file of Pure API function declarations. - * - * Explicitly no copyright. - * You may recompile and redistribute these definitions as required. - * - * NOTE1: In some situations when compiling with MFC, you should - * enable the setting 'Not using precompiled headers' in Visual C++ - * to avoid a compiler diagnostic. - * - * NOTE2: This file works through the use of deep magic. Calls to functions - * in this file are replaced with calls into the OCI runtime system - * when an instrumented version of this program is run. - * - * NOTE3: The static vars avoidGy_n (where n is a unique number) are used - * to prevent optimizing the functions away when compiler option - * /Gy is set. This is needed so that NOTE2 works properly. - */ - -// Chromium note: We used to only compile this code if PURIFY was defined, -// because we did special builds with all optimizations turned off for Purify. -// However, for profiling with Quantify, we want most/all optimizations turned -// on so that we measure something closer to real execution. - -#ifdef _WINDOWS // we only use Purify/Quantify on Windows - -#pragma once - extern int errno; -typedef int ptrdiff_t; -typedef unsigned int size_t; -typedef unsigned short wchar_t; -static int avoidGy_1 = 0; -static int avoidGy_2 = 0; -static int avoidGy_3 = 0; -static int avoidGy_4 = 0; -static int avoidGy_5 = 0; -static int avoidGy_6 = 0; -static int avoidGy_7 = 0; -static int avoidGy_8 = 0; -static int avoidGy_9 = 0; -static int avoidGy_10 = 0; -static int avoidGy_11 = 0; -static int avoidGy_12 = 0; -static int avoidGy_13 = 0; -static int avoidGy_14 = 0; -static int avoidGy_15 = 0; -static int avoidGy_16 = 0; -static int avoidGy_17 = 0; -static int avoidGy_18 = 0; -static int avoidGy_19 = 0; -static int avoidGy_20 = 0; -static int avoidGy_21 = 0; -static int avoidGy_22 = 0; -static int avoidGy_23 = 0; -static int avoidGy_24 = 0; -static int avoidGy_25 = 0; -static int avoidGy_26 = 0; -static int avoidGy_27 = 0; -static int avoidGy_28 = 0; -static int avoidGy_29 = 0; -static int avoidGy_30 = 0; -static int avoidGy_31 = 0; -static int avoidGy_32 = 0; -static int avoidGy_33 = 0; -static int avoidGy_34 = 0; -static int avoidGy_35 = 0; -static int avoidGy_36 = 0; -static int avoidGy_37 = 0; -static int avoidGy_38 = 0; -static int avoidGy_39 = 0; -static int avoidGy_40 = 0; -static int avoidGy_41 = 0; -static int avoidGy_42 = 0; -static int avoidGy_43 = 0; -static int avoidGy_44 = 0; -static int avoidGy_45 = 0; -static int avoidGy_46 = 0; -static int avoidGy_47 = 0; -static int avoidGy_48 = 0; -static int avoidGy_49 = 0; -static int avoidGy_50 = 0; -static int avoidGy_51 = 0; -static int avoidGy_52 = 0; -static int avoidGy_53 = 0; -static int avoidGy_54 = 0; -static int avoidGy_55 = 0; -static int avoidGy_56 = 0; -static int avoidGy_57 = 0; -static int avoidGy_58 = 0; -static int avoidGy_59 = 0; -static int avoidGy_60 = 0; -static int avoidGy_61 = 0; -static int avoidGy_62 = 0; -static int avoidGy_63 = 0; -static int avoidGy_64 = 0; -static int avoidGy_65 = 0; -static int avoidGy_PL_01 = 0; -static int avoidGy_PL_02 = 0; -__declspec(dllexport) int __cdecl PurePrintf(const char *fmt, ...) { if(!++avoidGy_1); fmt; return 0; } -__declspec(dllexport) int __cdecl PurifyIsRunning(void) { if(!++avoidGy_2); return 0; } -__declspec(dllexport) int __cdecl PurifyPrintf(const char *fmt, ...) { if(!++avoidGy_3); fmt; return 0; } -__declspec(dllexport) size_t __cdecl PurifyNewInuse(void) { if(!++avoidGy_4); return 0; } -__declspec(dllexport) size_t __cdecl PurifyAllInuse(void) { if(!++avoidGy_5); return 0; } -__declspec(dllexport) size_t __cdecl PurifyClearInuse(void) { if(!++avoidGy_6); return 0; } -__declspec(dllexport) size_t __cdecl PurifyNewLeaks(void) { if(!++avoidGy_7); return 0; } -__declspec(dllexport) size_t __cdecl PurifyAllLeaks(void) { if(!++avoidGy_8); return 0; } -__declspec(dllexport) size_t __cdecl PurifyClearLeaks(void) { if(!++avoidGy_9); return 0; } -__declspec(dllexport) size_t __cdecl PurifyAllHandlesInuse(void) { if(!++avoidGy_10); return 0; } -__declspec(dllexport) size_t __cdecl PurifyNewHandlesInuse(void) { if(!++avoidGy_11); return 0; } -__declspec(dllexport) size_t __cdecl PurifyDescribe(void *addr) { if(!++avoidGy_12); addr; return 0; } -__declspec(dllexport) int __cdecl PurifyWhatColors(void *addr, size_t size) { if(!++avoidGy_13); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyAssertIsReadable(const void *addr, size_t size) { if(!++avoidGy_14); addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyAssertIsWritable(const void *addr, size_t size) { if(!++avoidGy_15); addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyIsReadable(const void *addr, size_t size) { if(!++avoidGy_16); addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyIsWritable(const void *addr, size_t size) { if(!++avoidGy_17); addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyIsInitialized(const void *addr, size_t size) { if(!++avoidGy_18); addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyRed(void *addr, size_t size) { if(!++avoidGy_19); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyGreen(void *addr, size_t size) { if(!++avoidGy_20); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyYellow(void *addr, size_t size) { if(!++avoidGy_21); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyBlue(void *addr, size_t size) { if(!++avoidGy_22); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkAsInitialized(void *addr, size_t size) { if(!++avoidGy_23); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkAsUninitialized(void *addr, size_t size) { if(!++avoidGy_24); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkForTrap(void *addr, size_t size) { if(!++avoidGy_25); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkForNoTrap(void *addr, size_t size) { if(!++avoidGy_26); addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyHeapValidate(unsigned int hHeap, unsigned int dwFlags, const void *addr) - { if(!++avoidGy_27); hHeap; dwFlags; addr; return 1; } -__declspec(dllexport) int __cdecl PurifySetLateDetectScanCounter(int counter) { if(!++avoidGy_28); counter; return 0; }; -__declspec(dllexport) int __cdecl PurifySetLateDetectScanInterval(int seconds) { if(!++avoidGy_29); seconds; return 0; }; -__declspec(dllexport) void __cdecl PurifySetPoolId(const void *mem, int id) { if(!++avoidGy_61); mem; id; return; }; -__declspec(dllexport) int __cdecl PurifyGetPoolId(const void *mem) { if(!++avoidGy_62); mem; return 0; }; -__declspec(dllexport) void __cdecl PurifySetUserData(const void *mem, void *data) { if(!++avoidGy_63); mem; data; return; }; -__declspec(dllexport) void * __cdecl PurifyGetUserData(const void *mem) { if(!++avoidGy_64); mem; return 0; }; -__declspec(dllexport) void __cdecl PurifyMapPool(int id, void(*fn)()) { if(!++avoidGy_65); id; fn; return; }; -__declspec(dllexport) int __cdecl CoverageIsRunning(void) { if(!++avoidGy_30); return 0; } -__declspec(dllexport) int __cdecl CoverageDisableRecordingData(void) { if(!++avoidGy_31); return 0; } -__declspec(dllexport) int __cdecl CoverageStartRecordingData(void) { if(!++avoidGy_32); return 0; } -__declspec(dllexport) int __cdecl CoverageStopRecordingData(void) { if(!++avoidGy_33); return 0; } -__declspec(dllexport) int __cdecl CoverageClearData(void) { if(!++avoidGy_34); return 0; } -__declspec(dllexport) int __cdecl CoverageIsRecordingData(void) { if(!++avoidGy_35); return 0; } -__declspec(dllexport) int __cdecl CoverageAddAnnotation(char *str) { if(!++avoidGy_36); str; return 0; } -__declspec(dllexport) int __cdecl CoverageSaveData(void) { if(!++avoidGy_37); return 0; } -__declspec(dllexport) int __cdecl QuantifyIsRunning(void) { if(!++avoidGy_42); return 0; } -__declspec(dllexport) int __cdecl QuantifyDisableRecordingData(void) { if(!++avoidGy_43); return 0; } -__declspec(dllexport) int __cdecl QuantifyStartRecordingData(void) { if(!++avoidGy_44); return 0; } -__declspec(dllexport) int __cdecl QuantifyStopRecordingData(void) { if(!++avoidGy_45); return 0; } -__declspec(dllexport) int __cdecl QuantifyClearData(void) { if(!++avoidGy_46); return 0; } -__declspec(dllexport) int __cdecl QuantifyIsRecordingData(void) { if(!++avoidGy_47); return 0; } -__declspec(dllexport) int __cdecl QuantifyAddAnnotation(char *str) { if(!++avoidGy_48); str; return 0; } -__declspec(dllexport) int __cdecl QuantifySaveData(void) { if(!++avoidGy_49); return 0; } -__declspec(dllexport) int __cdecl QuantifySetThreadName(const char *szName) { if(!++avoidGy_50) ; szName; return 0; } - -#endif // _WINDOWS
--- a/ipc/glue/ProtocolUtils.h +++ b/ipc/glue/ProtocolUtils.h @@ -142,17 +142,17 @@ public: { mNeckoParent = aNeckoParent; } NeckoParent* GetNeckoParent() { return mNeckoParent; } }; template<class ListenerT> -class /*NS_INTERFACE_CLASS*/ IProtocolManager +class IProtocolManager { public: enum ActorDestroyReason { FailedConstructor, Deletion, AncestorDeletion, NormalShutdown, AbnormalShutdown
--- a/ipc/ipdl/ipdl/cxx/cgen.py +++ b/ipc/ipdl/ipdl/cxx/cgen.py @@ -120,27 +120,21 @@ class CxxCodeGen(CodePrinter, Visitor): self.visitDecl(p) if p.default is not None: self.write(' = ') p.default.accept(self) def visitClass(self, c): if c.specializes is not None: self.printdentln('template<>') - + if c.struct: self.printdent('struct') else: self.printdent('class') - if c.interface: - # FIXME/cjones: turn this "on" when we get the analysis - self.write(' /*NS_INTERFACE_CLASS*/') - if c.abstract: - # FIXME/cjones: turn this "on" when we get the analysis - self.write(' /*NS_ABSTRACT_CLASS*/') self.write(' '+ c.name) if c.final: self.write(' final') if c.specializes is not None: self.write(' <') c.specializes.accept(self) self.write('>')
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1170355.js @@ -0,0 +1,3 @@ +x = Array(4294967295); +x[1] = 0; +Array.prototype.shift.call(x);
--- a/js/src/jit/BacktrackingAllocator.cpp +++ b/js/src/jit/BacktrackingAllocator.cpp @@ -320,24 +320,29 @@ VirtualRegister::setInitialDefinition(Co { LiveRange* first = LiveRange::get(*rangesBegin()); MOZ_ASSERT(from >= first->from()); first->setFrom(from); first->setHasDefinition(); } LiveRange* -VirtualRegister::rangeFor(CodePosition pos) const +VirtualRegister::rangeFor(CodePosition pos, bool preferRegister /* = false */) const { + LiveRange* found = nullptr; for (LiveRange::RegisterLinkIterator iter = rangesBegin(); iter; iter++) { LiveRange* range = LiveRange::get(*iter); - if (range->covers(pos)) - return range; + if (range->covers(pos)) { + if (!preferRegister || range->bundle()->allocation().isRegister()) + return range; + if (!found) + found = range; + } } - return nullptr; + return found; } void VirtualRegister::addRange(LiveRange* range) { InsertSortedList(ranges_, &range->registerLink); } @@ -1701,17 +1706,17 @@ BacktrackingAllocator::resolveControlFlo { skip = true; break; } } if (skip) continue; - LiveRange* predecessorRange = reg.rangeFor(start.previous()); + LiveRange* predecessorRange = reg.rangeFor(start.previous(), /* preferRegister = */ true); if (start.subpos() == CodePosition::INPUT) { if (!moveInput(ins->toInstruction(), predecessorRange, range, reg.type())) return false; } else { if (!moveAfter(ins->toInstruction(), predecessorRange, range, reg.type())) return false; } } @@ -1737,17 +1742,17 @@ BacktrackingAllocator::resolveControlFlo LiveRange* to = reg.rangeFor(entryOf(successor)); MOZ_ASSERT(to); for (size_t k = 0; k < mSuccessor->numPredecessors(); k++) { LBlock* predecessor = mSuccessor->getPredecessor(k)->lir(); MOZ_ASSERT(predecessor->mir()->numSuccessors() == 1); LAllocation* input = phi->getOperand(k); - LiveRange* from = vreg(input).rangeFor(exitOf(predecessor)); + LiveRange* from = vreg(input).rangeFor(exitOf(predecessor), /* preferRegister = */ true); MOZ_ASSERT(from); if (!moveAtExit(predecessor, from, to, def->type())) return false; } } // Add moves to resolve graph edges with different allocations at their @@ -1762,17 +1767,17 @@ BacktrackingAllocator::resolveControlFlo for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { LiveRange* to = LiveRange::get(*iter); if (!to->covers(entryOf(successor))) continue; if (to->covers(exitOf(predecessor))) continue; - LiveRange* from = reg.rangeFor(exitOf(predecessor)); + LiveRange* from = reg.rangeFor(exitOf(predecessor), /* preferRegister = */ true); if (mSuccessor->numPredecessors() > 1) { MOZ_ASSERT(predecessor->mir()->numSuccessors() == 1); if (!moveAtExit(predecessor, from, to, reg.type())) return false; } else { if (!moveAtEntry(successor, from, to, reg.type())) return false; @@ -2321,21 +2326,22 @@ bool BacktrackingAllocator::minimalDef(LiveRange* range, LNode* ins) { // Whether this is a minimal range capturing a definition at ins. return (range->to() <= minimalDefEnd(ins).next()) && ((!ins->isPhi() && range->from() == inputOf(ins)) || range->from() == outputOf(ins)); } bool -BacktrackingAllocator::minimalUse(LiveRange* range, LNode* ins) +BacktrackingAllocator::minimalUse(LiveRange* range, UsePosition* use) { - // Whether this is a minimal range capturing a use at ins. + // Whether this is a minimal range capturing |use|. + LNode* ins = insData[use->pos]; return (range->from() == inputOf(ins)) && - (range->to() == outputOf(ins) || range->to() == outputOf(ins).next()); + (range->to() == (use->use->usedAtStart() ? outputOf(ins) : outputOf(ins).next())); } bool BacktrackingAllocator::minimalBundle(LiveBundle* bundle, bool* pfixed) { LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); LiveRange* range = LiveRange::get(*iter); @@ -2363,22 +2369,22 @@ BacktrackingAllocator::minimalBundle(Liv multiple = true; LUse* use = iter->use; switch (use->policy()) { case LUse::FIXED: if (fixed) return false; fixed = true; - if (minimalUse(range, insData[iter->pos])) + if (minimalUse(range, *iter)) minimal = true; break; case LUse::REGISTER: - if (minimalUse(range, insData[iter->pos])) + if (minimalUse(range, *iter)) minimal = true; break; default: break; } }
--- a/js/src/jit/BacktrackingAllocator.h +++ b/js/src/jit/BacktrackingAllocator.h @@ -519,17 +519,17 @@ class VirtualRegister return !!rangesBegin(); } LiveRange* firstRange() const { return LiveRange::get(*rangesBegin()); } LiveRange* lastRange() const { return LiveRange::get(ranges_.back()); } - LiveRange* rangeFor(CodePosition pos) const; + LiveRange* rangeFor(CodePosition pos, bool preferRegister = false) const; void removeRange(LiveRange* range); void addRange(LiveRange* range); LiveBundle* firstBundle() const { return firstRange()->bundle(); } bool addInitialRange(TempAllocator& alloc, CodePosition from, CodePosition to); @@ -702,17 +702,17 @@ class BacktrackingAllocator : protected // Debugging methods. void dumpFixedRanges(); void dumpAllocations(); struct PrintLiveRange; bool minimalDef(LiveRange* range, LNode* ins); - bool minimalUse(LiveRange* range, LNode* ins); + bool minimalUse(LiveRange* range, UsePosition* use); bool minimalBundle(LiveBundle* bundle, bool* pfixed = nullptr); // Heuristic methods. size_t computePriority(LiveBundle* bundle); size_t computeSpillWeight(LiveBundle* bundle); size_t maximumSpillWeight(const LiveBundleVector& bundles);
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2203,18 +2203,24 @@ CodeGenerator::visitMoveGroup(LMoveGroup } masm.propagateOOM(resolver.addMove(toMoveOperand(from), toMoveOperand(to), moveType)); } masm.propagateOOM(resolver.resolve()); MoveEmitter emitter(masm); + +#ifdef JS_CODEGEN_X86 if (group->maybeScratchRegister().isGeneralReg()) emitter.setScratchRegister(group->maybeScratchRegister().toGeneralReg()->reg()); + else + resolver.sortMemoryToMemoryMoves(); +#endif + emitter.emit(resolver); emitter.finish(); } void CodeGenerator::visitInteger(LInteger* lir) { masm.move32(Imm32(lir->getValue()), ToRegister(lir->output()));
--- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -114,24 +114,20 @@ class LMoveGroup : public LInstructionHe const LMove& getMove(size_t i) const { return moves_[i]; } #ifdef JS_CODEGEN_X86 void setScratchRegister(Register reg) { scratchRegister_ = LGeneralReg(reg); } -#endif LAllocation maybeScratchRegister() { -#ifdef JS_CODEGEN_X86 return scratchRegister_; -#else - return LAllocation(); + } #endif - } bool uses(Register reg) { for (size_t i = 0; i < numMoves(); i++) { LMove move = getMove(i); if (move.from() == LGeneralReg(reg) || move.to() == LGeneralReg(reg)) return true; } return false;
--- a/js/src/jit/MoveResolver.cpp +++ b/js/src/jit/MoveResolver.cpp @@ -196,19 +196,95 @@ MoveResolver::addOrderedMove(const MoveO return orderedMoves_.insert(after, nmove); } else if (move.to().isGeneralReg() || move.to().isFloatReg()) { MoveOp nmove(move.to(), existing.to(), move.type()); orderedMoves_[i] = move; return orderedMoves_.insert(after, nmove); } } - if (existing.to().aliases(move.from()) || - existing.to().aliases(move.to()) || - existing.from().aliases(move.to()) || - existing.from().aliases(move.from())) - { + if (existing.aliases(move)) break; - } } return orderedMoves_.append(move); } + +void +MoveResolver::reorderMove(size_t from, size_t to) +{ + MOZ_ASSERT(from != to); + + MoveOp op = orderedMoves_[from]; + if (from < to) { + for (size_t i = from; i < to; i++) + orderedMoves_[i] = orderedMoves_[i + 1]; + } else { + for (size_t i = from; i > to; i--) + orderedMoves_[i] = orderedMoves_[i - 1]; + } + orderedMoves_[to] = op; +} + +void +MoveResolver::sortMemoryToMemoryMoves() +{ + // Try to reorder memory->memory moves so that they are executed right + // before a move that clobbers some register. This will allow the move + // emitter to use that clobbered register as a scratch register for the + // memory->memory move, if necessary. + for (size_t i = 0; i < orderedMoves_.length(); i++) { + const MoveOp& base = orderedMoves_[i]; + if (!base.from().isMemory() || !base.to().isMemory()) + continue; + if (base.type() != MoveOp::GENERAL && base.type() != MoveOp::INT32) + continue; + + // Look for an earlier move clobbering a register. + bool found = false; + for (int j = i - 1; j >= 0; j--) { + const MoveOp& previous = orderedMoves_[j]; + if (previous.aliases(base) || previous.isCycleBegin() || previous.isCycleEnd()) + break; + + if (previous.to().isGeneralReg()) { + reorderMove(i, j); + found = true; + break; + } + } + if (found) + continue; + + // Look for a later move clobbering a register. + if (i + 1 < orderedMoves_.length()) { + bool found = false, skippedRegisterUse = false; + for (size_t j = i + 1; j < orderedMoves_.length(); j++) { + const MoveOp& later = orderedMoves_[j]; + if (later.aliases(base) || later.isCycleBegin() || later.isCycleEnd()) + break; + + if (later.to().isGeneralReg()) { + if (skippedRegisterUse) { + reorderMove(i, j); + found = true; + } else { + // There is no move that uses a register between the + // original memory->memory move and this move that + // clobbers a register. The move should already be able + // to use a scratch register, so don't shift anything + // around. + } + break; + } + + if (later.from().isGeneralReg()) + skippedRegisterUse = true; + } + + if (found) { + // Redo the search for memory->memory moves at the current + // index, so we don't skip the move just shifted back. + i--; + } + } + } +}
--- a/js/src/jit/MoveResolver.h +++ b/js/src/jit/MoveResolver.h @@ -192,16 +192,22 @@ class MoveOp } Type type() const { return type_; } Type endCycleType() const { MOZ_ASSERT(isCycleBegin()); return endCycleType_; } + bool aliases(const MoveOperand& op) const { + return from().aliases(op) || to().aliases(op); + } + bool aliases(const MoveOp& other) const { + return aliases(other.from()) || aliases(other.to()); + } }; class MoveResolver { private: struct PendingMove : public MoveOp, public TempObject, @@ -224,44 +230,44 @@ class MoveResolver cycleEnd_ = true; cycleEndSlot_ = cycleSlot; } }; typedef InlineList<MoveResolver::PendingMove>::iterator PendingMoveIterator; private: - // Moves that are definitely unblocked (constants to registers). These are - // emitted last. js::Vector<MoveOp, 16, SystemAllocPolicy> orderedMoves_; int numCycles_; int curCycles_; TempObjectPool<PendingMove> movePool_; InlineList<PendingMove> pending_; PendingMove* findBlockingMove(const PendingMove* last); PendingMove* findCycledMove(PendingMoveIterator* stack, PendingMoveIterator end, const PendingMove* first); bool addOrderedMove(const MoveOp& move); + void reorderMove(size_t from, size_t to); // Internal reset function. Does not clear lists. void resetState(); public: MoveResolver(); // Resolves a move group into two lists of ordered moves. These moves must // be executed in the order provided. Some moves may indicate that they // participate in a cycle. For every cycle there are two such moves, and it // is guaranteed that cycles do not nest inside each other in the list. // // After calling addMove() for each parallel move, resolve() performs the // cycle resolution algorithm. Calling addMove() again resets the resolver. bool addMove(const MoveOperand& from, const MoveOperand& to, MoveOp::Type type); bool resolve(); + void sortMemoryToMemoryMoves(); size_t numMoves() const { return orderedMoves_.length(); } const MoveOp& getMove(size_t i) const { return orderedMoves_[i]; } uint32_t numCycles() const {
--- a/js/src/jit/x86-shared/MoveEmitter-x86-shared.cpp +++ b/js/src/jit/x86-shared/MoveEmitter-x86-shared.cpp @@ -6,16 +6,18 @@ #include "jit/x86-shared/MoveEmitter-x86-shared.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; +using mozilla::Maybe; + MoveEmitterX86::MoveEmitterX86(MacroAssembler& masm) : inCycle_(false), masm(masm), pushedAtCycle_(-1) { pushedAtStart_ = masm.framePushed(); } @@ -96,21 +98,29 @@ MoveEmitterX86::maybeEmitOptimizedCycle( return false; } void MoveEmitterX86::emit(const MoveResolver& moves) { #if defined(JS_CODEGEN_X86) && defined(DEBUG) // Clobber any scratch register we have, to make regalloc bugs more visible. - if (hasScratchRegister()) - masm.mov(ImmWord(0xdeadbeef), scratchRegister()); + if (scratchRegister_.isSome()) + masm.mov(ImmWord(0xdeadbeef), scratchRegister_.value()); #endif for (size_t i = 0; i < moves.numMoves(); i++) { +#if defined(JS_CODEGEN_X86) && defined(DEBUG) + if (!scratchRegister_.isSome()) { + Maybe<Register> reg = findScratchRegister(moves, i); + if (reg.isSome()) + masm.mov(ImmWord(0xdeadbeef), reg.value()); + } +#endif + const MoveOp& move = moves.getMove(i); const MoveOperand& from = move.from(); const MoveOperand& to = move.to(); if (move.isCycleEnd()) { MOZ_ASSERT(inCycle_); completeCycle(to, move.type()); inCycle_ = false; @@ -139,20 +149,20 @@ MoveEmitterX86::emit(const MoveResolver& switch (move.type()) { case MoveOp::FLOAT32: emitFloat32Move(from, to); break; case MoveOp::DOUBLE: emitDoubleMove(from, to); break; case MoveOp::INT32: - emitInt32Move(from, to); + emitInt32Move(from, to, moves, i); break; case MoveOp::GENERAL: - emitGeneralMove(from, to); + emitGeneralMove(from, to, moves, i); break; case MoveOp::INT32X4: emitInt32X4Move(from, to); break; case MoveOp::FLOAT32X4: emitFloat32X4Move(from, to); break; default: @@ -358,67 +368,69 @@ MoveEmitterX86::completeCycle(const Move masm.Pop(toPopOperand(to)); break; default: MOZ_CRASH("Unexpected move type"); } } void -MoveEmitterX86::emitInt32Move(const MoveOperand& from, const MoveOperand& to) +MoveEmitterX86::emitInt32Move(const MoveOperand& from, const MoveOperand& to, + const MoveResolver& moves, size_t i) { if (from.isGeneralReg()) { masm.move32(from.reg(), toOperand(to)); } else if (to.isGeneralReg()) { MOZ_ASSERT(from.isMemory()); masm.load32(toAddress(from), to.reg()); } else { // Memory to memory gpr move. MOZ_ASSERT(from.isMemory()); - if (hasScratchRegister()) { - Register reg = scratchRegister(); - masm.load32(toAddress(from), reg); - masm.move32(reg, toOperand(to)); + Maybe<Register> reg = findScratchRegister(moves, i); + if (reg.isSome()) { + masm.load32(toAddress(from), reg.value()); + masm.move32(reg.value(), toOperand(to)); } else { // No scratch register available; bounce it off the stack. masm.Push(toOperand(from)); masm.Pop(toPopOperand(to)); } } } void -MoveEmitterX86::emitGeneralMove(const MoveOperand& from, const MoveOperand& to) +MoveEmitterX86::emitGeneralMove(const MoveOperand& from, const MoveOperand& to, + const MoveResolver& moves, size_t i) { if (from.isGeneralReg()) { masm.mov(from.reg(), toOperand(to)); } else if (to.isGeneralReg()) { MOZ_ASSERT(from.isMemoryOrEffectiveAddress()); if (from.isMemory()) masm.loadPtr(toAddress(from), to.reg()); else masm.lea(toOperand(from), to.reg()); } else if (from.isMemory()) { // Memory to memory gpr move. - if (hasScratchRegister()) { - Register reg = scratchRegister(); - masm.loadPtr(toAddress(from), reg); - masm.mov(reg, toOperand(to)); + Maybe<Register> reg = findScratchRegister(moves, i); + if (reg.isSome()) { + masm.loadPtr(toAddress(from), reg.value()); + masm.mov(reg.value(), toOperand(to)); } else { // No scratch register available; bounce it off the stack. masm.Push(toOperand(from)); masm.Pop(toPopOperand(to)); } } else { // Effective address to memory move. MOZ_ASSERT(from.isEffectiveAddress()); - if (hasScratchRegister()) { - Register reg = scratchRegister(); - masm.lea(toOperand(from), reg); - masm.mov(reg, toOperand(to)); + Maybe<Register> reg = findScratchRegister(moves, i); + if (reg.isSome()) { + masm.lea(toOperand(from), reg.value()); + masm.mov(reg.value(), toOperand(to)); } else { // This is tricky without a scratch reg. We can't do an lea. Bounce the // base register off the stack, then add the offset in place. Note that // this clobbers FLAGS! masm.Push(from.base()); masm.Pop(toPopOperand(to)); masm.addPtr(Imm32(from.disp()), toOperand(to)); } @@ -518,8 +530,39 @@ MoveEmitterX86::assertDone() void MoveEmitterX86::finish() { assertDone(); masm.freeStack(masm.framePushed() - pushedAtStart_); } +Maybe<Register> +MoveEmitterX86::findScratchRegister(const MoveResolver& moves, size_t initial) +{ +#ifdef JS_CODEGEN_X86 + if (scratchRegister_.isSome()) + return scratchRegister_; + + // All registers are either in use by this move group or are live + // afterwards. Look through the remaining moves for a register which is + // clobbered before it is used, and is thus dead at this point. + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); + for (size_t i = initial; i < moves.numMoves(); i++) { + const MoveOp& move = moves.getMove(i); + if (move.from().isGeneralReg()) + regs.takeUnchecked(move.from().reg()); + else if (move.from().isMemoryOrEffectiveAddress()) + regs.takeUnchecked(move.from().base()); + if (move.to().isGeneralReg()) { + if (i != initial && !move.isCycleBegin() && regs.has(move.to().reg())) + return mozilla::Some(move.to().reg()); + regs.takeUnchecked(move.to().reg()); + } else if (move.to().isMemoryOrEffectiveAddress()) { + regs.takeUnchecked(move.to().base()); + } + } + + return mozilla::Nothing(); +#else + return mozilla::Some(ScratchReg); +#endif +}
--- a/js/src/jit/x86-shared/MoveEmitter-x86-shared.h +++ b/js/src/jit/x86-shared/MoveEmitter-x86-shared.h @@ -35,18 +35,20 @@ class MoveEmitterX86 Address toAddress(const MoveOperand& operand) const; Operand toOperand(const MoveOperand& operand) const; Operand toPopOperand(const MoveOperand& operand) const; size_t characterizeCycle(const MoveResolver& moves, size_t i, bool* allGeneralRegs, bool* allFloatRegs); bool maybeEmitOptimizedCycle(const MoveResolver& moves, size_t i, bool allGeneralRegs, bool allFloatRegs, size_t swapCount); - void emitInt32Move(const MoveOperand& from, const MoveOperand& to); - void emitGeneralMove(const MoveOperand& from, const MoveOperand& to); + void emitInt32Move(const MoveOperand& from, const MoveOperand& to, + const MoveResolver& moves, size_t i); + void emitGeneralMove(const MoveOperand& from, const MoveOperand& to, + const MoveResolver& moves, size_t i); void emitFloat32Move(const MoveOperand& from, const MoveOperand& to); void emitDoubleMove(const MoveOperand& from, const MoveOperand& to); void emitFloat32X4Move(const MoveOperand& from, const MoveOperand& to); void emitInt32X4Move(const MoveOperand& from, const MoveOperand& to); void breakCycle(const MoveOperand& to, MoveOp::Type type); void completeCycle(const MoveOperand& to, MoveOp::Type type); public: @@ -56,32 +58,17 @@ class MoveEmitterX86 void finish(); void setScratchRegister(Register reg) { #ifdef JS_CODEGEN_X86 scratchRegister_.emplace(reg); #endif } - bool hasScratchRegister() { -#ifdef JS_CODEGEN_X86 - return scratchRegister_.isSome(); -#else - return true; -#endif - } - - Register scratchRegister() { - MOZ_ASSERT(hasScratchRegister()); -#ifdef JS_CODEGEN_X86 - return scratchRegister_.value(); -#else - return ScratchReg; -#endif - } + mozilla::Maybe<Register> findScratchRegister(const MoveResolver& moves, size_t i); }; typedef MoveEmitterX86 MoveEmitter; } // ion } // js #endif /* jit_MoveEmitter_x86_shared_h */
--- a/js/src/jsapi-tests/testDefinePropertyIgnoredAttributes.cpp +++ b/js/src/jsapi-tests/testDefinePropertyIgnoredAttributes.cpp @@ -44,57 +44,57 @@ BEGIN_TEST(testDefinePropertyIgnoredAttr // Try a getter. Allow it to fill in the defaults. Because we're passing a // JSNative, JS_DefineProperty will infer JSPROP_GETTER even though we // aren't passing it. CHECK(JS_DefineProperty(cx, obj, "foo", defineValue, JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_PERMANENT | JSPROP_SHARED, Getter)); - CHECK(JS_GetPropertyDescriptor(cx, obj, "foo", &desc)); + CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "foo", &desc)); // Note that JSPROP_READONLY is meaningless for accessor properties. CHECK(CheckDescriptor(desc, AccessorDescriptor, false, true, false)); // Install another configurable property, so we can futz with it. CHECK(JS_DefineProperty(cx, obj, "bar", defineValue, JSPROP_IGNORE_ENUMERATE | JSPROP_SHARED, Getter)); - CHECK(JS_GetPropertyDescriptor(cx, obj, "bar", &desc)); + CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "bar", &desc)); CHECK(CheckDescriptor(desc, AccessorDescriptor, false, true, true)); // Rewrite the descriptor to now be enumerable, leaving the configurability // unchanged. CHECK(JS_DefineProperty(cx, obj, "bar", defineValue, JSPROP_IGNORE_PERMANENT | JSPROP_ENUMERATE | JSPROP_SHARED, Getter)); - CHECK(JS_GetPropertyDescriptor(cx, obj, "bar", &desc)); + CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "bar", &desc)); CHECK(CheckDescriptor(desc, AccessorDescriptor, true, true, true)); // Now try the same game with a value property defineValue.setObject(*obj); CHECK(JS_DefineProperty(cx, obj, "baz", defineValue, JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT)); - CHECK(JS_GetPropertyDescriptor(cx, obj, "baz", &desc)); + CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "baz", &desc)); CHECK(CheckDescriptor(desc, DataDescriptor, false, false, false)); // Now again with a configurable property CHECK(JS_DefineProperty(cx, obj, "quux", defineValue, JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY)); - CHECK(JS_GetPropertyDescriptor(cx, obj, "quux", &desc)); + CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "quux", &desc)); CHECK(CheckDescriptor(desc, DataDescriptor, false, false, true)); // Just make it writable. Leave the old value and everything else alone. defineValue.setUndefined(); CHECK(JS_DefineProperty(cx, obj, "quux", defineValue, JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_PERMANENT | JSPROP_IGNORE_VALUE)); - CHECK(JS_GetPropertyDescriptor(cx, obj, "quux", &desc)); + CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "quux", &desc)); CHECK(CheckDescriptor(desc, DataDescriptor, false, true, true)); CHECK_SAME(JS::ObjectValue(*obj), desc.value()); return true; } END_TEST(testDefinePropertyIgnoredAttributes)
--- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2164,17 +2164,20 @@ js::array_shift(JSContext* cx, unsigned DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj); if (result != DenseElementResult::Incomplete) { if (result == DenseElementResult::Failure) return false; if (!SetLengthProperty(cx, obj, newlen)) return false; - return SuppressDeletedProperty(cx, obj, INT_TO_JSID(newlen)); + RootedId id(cx); + if (!IndexToId(cx, newlen, &id)) + return false; + return SuppressDeletedProperty(cx, obj, id); } /* Steps 5, 10. */ bool hole; if (!GetElement(cx, obj, uint32_t(0), &hole, args.rval())) return false; /* Steps 6-7. */
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/newTargetNonFunction.js @@ -0,0 +1,10 @@ +// Make sure that we can plumb new.target, even if the results are going to +// throw. + +assertThrowsInstanceOf(() => new ""(...Array()), TypeError); + +assertThrowsInstanceOf(() => new ""(), TypeError); +assertThrowsInstanceOf(() => new ""(1), TypeError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4582,20 +4582,18 @@ js::SpreadCallOperation(JSContext* cx, H return false; args.setCallee(callee); args.setThis(thisv); if (!GetElements(cx, aobj, length, args.array())) return false; - if (constructing) { - MOZ_ASSERT(newTarget.isObject()); + if (constructing) args.newTarget().set(newTarget); - } switch (op) { case JSOP_SPREADNEW: if (!InvokeConstructor(cx, args)) return false; break; case JSOP_SPREADCALL: if (!Invoke(cx, args))
--- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -1256,35 +1256,43 @@ nsGridContainerFrame::ReflowChildren(Gri if (MOZ_LIKELY(isGridItem)) { GridArea* area = GetGridAreaForChild(child); MOZ_ASSERT(area && area->IsDefinite()); cb = ContainingBlockFor(wm, *area, aColSizes, aRowSizes); cb += gridOrigin; } else { cb = aContentArea; } - nsHTMLReflowState childRS(pc, aReflowState, child, cb.Size(wm)); + WritingMode childWM = child->GetWritingMode(); + LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); + nsHTMLReflowState childRS(pc, aReflowState, child, childCBSize); const LogicalMargin margin = childRS.ComputedLogicalMargin(); if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) { // XXX the start of an align-self:stretch impl. Needs min-/max-bsize // clamping though, and check the prop value is actually 'stretch'! LogicalMargin bp = childRS.ComputedLogicalBorderPadding(); bp.ApplySkipSides(child->GetLogicalSkipSides()); - nscoord bSize = cb.BSize(wm) - bp.BStartEnd(wm) - margin.BStartEnd(wm); + nscoord bSize = childCBSize.BSize(childWM) - bp.BStartEnd(childWM) - + margin.BStartEnd(childWM); childRS.SetComputedBSize(std::max(bSize, 0)); } - LogicalPoint childPos = cb.Origin(wm); - childPos.I(wm) += margin.IStart(wm); - childPos.B(wm) += margin.BStart(wm); + // We need the width of the child before we can correctly convert + // the writing-mode of its origin, so we reflow at (0, 0) and then + // pass the correct position to FinishReflowChild. nsHTMLReflowMetrics childSize(childRS); nsReflowStatus childStatus; - ReflowChild(child, pc, childSize, childRS, wm, childPos, - containerWidth, 0, childStatus); + ReflowChild(child, pc, childSize, childRS, childWM, LogicalPoint(childWM), + 0, 0, childStatus); + LogicalPoint childPos = + cb.Origin(wm).ConvertTo(childWM, wm, containerWidth - childSize.Width() - + margin.LeftRight(childWM)); + childPos.I(childWM) += margin.IStart(childWM); + childPos.B(childWM) += margin.BStart(childWM); childRS.ApplyRelativePositioning(&childPos, containerWidth); - FinishReflowChild(child, pc, childSize, &childRS, wm, childPos, + FinishReflowChild(child, pc, childSize, &childRS, childWM, childPos, containerWidth, 0); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); // XXX deal with 'childStatus' not being COMPLETE } if (IsAbsoluteContainer()) { nsFrameList children(GetChildList(GetAbsoluteListID())); if (!children.IsEmpty()) {
new file mode 100644 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-dir-001-ref.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<style type="text/css"> +.grid { + border: 1px solid gray; + font-size: 0; +} +.grid > div { + display: inline-block; + margin: 2px; +} +#a { background: #006; height: 46px; width: 96px; } +#b { background: #009; height: 46px; width: 146px; } +#c { background: #00c; height: 46px; width: 46px; } +#d { background: #00f; height: 46px; width: 16px; } +#e { background: #060; height: 96px; width: 96px; } +#f { background: #090; height: 96px; width: 146px; } +#g { background: #0c0; height: 96px; width: 46px; } +#h { background: #0f0; height: 96px; width: 16px; } +#i { background: #600; height: 16px; width: 96px; } +#j { background: #900; height: 16px; width: 146px; } +#k { background: #c00; height: 16px; width: 46px; } +#l { background: #f00; height: 16px; width: 16px; } +</style> +</head> + +<body> +<div class=grid> + <div id=a></div> + <div id=b></div> + <div id=c></div> + <div id=d></div><br> + <div id=e></div> + <div id=f></div> + <div id=g></div> + <div id=h></div><br> + <div id=i></div> + <div id=j></div> + <div id=k></div> + <div id=l></div> +</div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-dir-001.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<style type="text/css"> +.grid { + display: grid; + border: 1px solid gray; + grid-template-columns: 100px 150px 50px 20px; + grid-template-rows: 50px 100px 20px; +} +.grid > div { + margin: 2px; +} +#a { background: #006; } +#b { background: #009; } +#c { background: #00c; } +#d { background: #00f; } +#e { background: #060; } +#f { background: #090; } +#g { background: #0c0; } +#h { background: #0f0; } +#i { background: #600; } +#j { background: #900; } +#k { background: #c00; } +#l { background: #f00; } +</style> +</head> + +<body> +<div class=grid> + <div id=a></div> + <div id=b></div> + <div id=c></div> + <div id=d></div> + <div id=e dir=rtl></div> + <div id=f dir=rtl></div> + <div id=g dir=rtl></div> + <div id=h dir=rtl></div> + <div id=i></div> + <div id=j dir=rtl></div> + <div id=k></div> + <div id=l dir=rtl></div> +</div> +</body> +</html>
--- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -20,8 +20,9 @@ skip-if(Android) == grid-placement-defin == grid-placement-definite-implicit-002.html grid-placement-definite-implicit-002-ref.html skip-if(Android) == grid-placement-auto-implicit-001.html grid-placement-auto-implicit-001-ref.html == grid-placement-abspos-implicit-001.html grid-placement-abspos-implicit-001-ref.html pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-definite-001.html rtl-grid-placement-definite-001-ref.html pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-auto-row-sparse-001.html rtl-grid-placement-auto-row-sparse-001-ref.html pref(layout.css.vertical-text.enabled,true) == vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001-ref.html pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001-ref.html == grid-relpos-items-001.html grid-relpos-items-001-ref.html +== grid-item-dir-001.html grid-item-dir-001-ref.html
--- a/layout/style/ErrorReporter.cpp +++ b/layout/style/ErrorReporter.cpp @@ -238,17 +238,21 @@ ErrorReporter::AddToError(const nsString if (mError.IsEmpty()) { mError = aErrorText; mErrorLineNumber = mScanner->GetLineNumber(); mErrorColNumber = mScanner->GetColumnNumber(); // Retrieve the error line once per line, and reuse the same nsString // for all errors on that line. That causes the text of the line to // be shared among all the nsIScriptError objects. if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) { - mErrorLine = mScanner->GetCurrentLine(); + // Be careful here: the error line might be really long and OOM + // when we try to make a copy here. If so, just leave it empty. + if (!mErrorLine.Assign(mScanner->GetCurrentLine(), fallible)) { + mErrorLine.Truncate(); + } mPrevErrorLineNumber = mErrorLineNumber; } } else { mError.AppendLiteral(" "); mError.Append(aErrorText); } }
--- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -154,46 +154,43 @@ nsTableFrame::nsTableFrame(nsStyleContex } void nsTableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { NS_PRECONDITION(!mCellMap, "Init called twice"); + NS_PRECONDITION(!mTableLayoutStrategy, "Init called twice"); NS_PRECONDITION(!aPrevInFlow || aPrevInFlow->GetType() == nsGkAtoms::tableFrame, "prev-in-flow must be of same type"); // Let the base class do its processing nsContainerFrame::Init(aContent, aParent, aPrevInFlow); // see if border collapse is on, if so set it const nsStyleTableBorder* tableStyle = StyleTableBorder(); bool borderCollapse = (NS_STYLE_BORDER_COLLAPSE == tableStyle->mBorderCollapse); SetBorderCollapse(borderCollapse); - // Create the cell map if this frame is the first-in-flow. if (!aPrevInFlow) { + // If we're the first-in-flow, we manage the cell map & layout strategy that + // get used by our continuation chain: mCellMap = new nsTableCellMap(*this, borderCollapse); - } - - if (aPrevInFlow) { + if (IsAutoLayout()) { + mTableLayoutStrategy = new BasicTableLayoutStrategy(this); + } else { + mTableLayoutStrategy = new FixedTableLayoutStrategy(this); + } + } else { // set my width, because all frames in a table flow are the same width and // code in nsTableOuterFrame depends on this being set mRect.width = aPrevInFlow->GetSize().width; } - else { - NS_ASSERTION(!mTableLayoutStrategy, "strategy was created before Init was called"); - // create the strategy - if (IsAutoLayout()) - mTableLayoutStrategy = new BasicTableLayoutStrategy(this); - else - mTableLayoutStrategy = new FixedTableLayoutStrategy(this); - } } nsTableFrame::~nsTableFrame() { delete mCellMap; delete mTableLayoutStrategy; } @@ -300,18 +297,17 @@ nsTableFrame::UnregisterPositionedTableP tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation()); // Retrieve the positioned parts array for this table. FrameProperties props = tableFrame->Properties(); auto positionedParts = static_cast<FrameTArray*>(props.Get(PositionedTablePartArray())); // Remove the frame. - MOZ_ASSERT(positionedParts && - positionedParts->IndexOf(aFrame) != FrameTArray::NoIndex, + MOZ_ASSERT(positionedParts && positionedParts->Contains(aFrame), "Asked to unregister a positioned table part that wasn't registered"); if (positionedParts) { positionedParts->RemoveElement(aFrame); } } // XXX this needs to be cleaned up so that the frame constructor breaks out col group // frames into a separate child list, bug 343048. @@ -1787,17 +1783,17 @@ nsTableFrame::Reflow(nsPresContext* { MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsTableFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); bool isPaginated = aPresContext->IsPaginated(); aStatus = NS_FRAME_COMPLETE; if (!GetPrevInFlow() && !mTableLayoutStrategy) { - NS_ASSERTION(false, "strategy should have been created in Init"); + NS_ERROR("strategy should have been created in Init"); return; } // see if collapsing borders need to be calculated if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) { CalcBCBorders(); } @@ -1887,17 +1883,17 @@ nsTableFrame::Reflow(nsPresContext* } haveDesiredHeight = true; mutable_rs.mFlags.mSpecialHeightReflow = false; } } else { // Calculate the overflow area contribution from our children. - for (nsIFrame* kid = GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { + for (nsIFrame* kid : mFrames) { ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid); } } aDesiredSize.Width() = aReflowState.ComputedWidth() + aReflowState.ComputedPhysicalBorderPadding().LeftRight(); if (!haveDesiredHeight) { CalcDesiredHeight(aReflowState, aDesiredSize); @@ -2046,17 +2042,17 @@ nsTableFrame::ReflowTable(nsHTMLReflowMe } nsIFrame* nsTableFrame::GetFirstBodyRowGroupFrame() { nsIFrame* headerFrame = nullptr; nsIFrame* footerFrame = nullptr; - for (nsIFrame* kidFrame = mFrames.FirstChild(); nullptr != kidFrame; ) { + for (nsIFrame* kidFrame : mFrames) { const nsStyleDisplay* childDisplay = kidFrame->StyleDisplay(); // We expect the header and footer row group frames to be first, and we only // allow one header and one footer if (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP == childDisplay->mDisplay) { if (headerFrame) { // We already have a header frame and so this header frame is treated // like an ordinary body row group frame @@ -2070,19 +2066,16 @@ nsTableFrame::GetFirstBodyRowGroupFrame( // like an ordinary body row group frame return kidFrame; } footerFrame = kidFrame; } else if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == childDisplay->mDisplay) { return kidFrame; } - - // Get the next child - kidFrame = kidFrame->GetNextSibling(); } return nullptr; } // Table specific version that takes into account repeated header and footer // frames when continuing table frames void @@ -2165,18 +2158,17 @@ nsTableFrame::AdjustForCollapsingRowsCol nscoord nsTableFrame::GetCollapsedWidth(nsMargin aBorderPadding) { NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedWidth called on next in flow"); nscoord width = GetColSpacing(GetColCount()); width += aBorderPadding.left + aBorderPadding.right; - for (nsIFrame* groupFrame = mColGroups.FirstChild(); groupFrame; - groupFrame = groupFrame->GetNextSibling()) { + for (nsIFrame* groupFrame : mColGroups) { const nsStyleVisibility* groupVis = groupFrame->StyleVisibility(); bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame; for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame; colFrame = colFrame->GetNextCol()) { const nsStyleDisplay* colDisplay = colFrame->StyleDisplay(); int32_t colX = colFrame->GetColIndex(); if (NS_STYLE_DISPLAY_TABLE_COLUMN == colDisplay->mDisplay) { @@ -3195,18 +3187,17 @@ nsTableFrame::ReflowChildren(nsTableRefl } void nsTableFrame::ReflowColGroups(nsRenderingContext *aRenderingContext) { if (!GetPrevInFlow() && !HaveReflowedColGroups()) { nsHTMLReflowMetrics kidMet(GetWritingMode()); nsPresContext *presContext = PresContext(); - for (nsIFrame* kidFrame = mColGroups.FirstChild(); kidFrame; - kidFrame = kidFrame->GetNextSibling()) { + for (nsIFrame* kidFrame : mColGroups) { if (NS_SUBTREE_DIRTY(kidFrame)) { // The column groups don't care about dimensions or reflow states. nsHTMLReflowState kidReflowState(presContext, kidFrame, aRenderingContext, LogicalSize(kidFrame->GetWritingMode())); nsReflowStatus cgStatus; ReflowChild(kidFrame, presContext, kidMet, kidReflowState, 0, 0, 0, cgStatus); @@ -3218,17 +3209,17 @@ nsTableFrame::ReflowColGroups(nsRenderin } void nsTableFrame::CalcDesiredHeight(const nsHTMLReflowState& aReflowState, nsHTMLReflowMetrics& aDesiredSize) { nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) { - NS_ASSERTION(false, "never ever call me until the cell map is built!"); + NS_ERROR("never ever call me until the cell map is built!"); aDesiredSize.Height() = 0; return; } nsMargin borderPadding = GetChildAreaOffset(&aReflowState); // get the natural height based on the last child's (row group) rect RowGroupArray rowGroups; OrderRowGroups(rowGroups); @@ -3262,17 +3253,17 @@ nsTableFrame::CalcDesiredHeight(const ns nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); if ((tableSpecifiedHeight > 0) && (tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE) && (tableSpecifiedHeight > desiredHeight)) { // proportionately distribute the excess height to unconstrained rows in each // unconstrained row group. DistributeHeightToRows(aReflowState, tableSpecifiedHeight - desiredHeight); // this might have changed the overflow area incorporate the childframe overflow area. - for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; kidFrame = kidFrame->GetNextSibling()) { + for (nsIFrame* kidFrame : mFrames) { ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); } desiredHeight = tableSpecifiedHeight; } } aDesiredSize.Height() = desiredHeight; } @@ -3870,18 +3861,17 @@ nsTableFrame::Dump(bool aDump printf(" anonymous-colgroup "); break; case eColAnonymousCell: printf(" anonymous-cell "); break; } } printf("\n colgroups->"); - for (nsIFrame* childFrame = mColGroups.FirstChild(); childFrame; - childFrame = childFrame->GetNextSibling()) { + for (nsIFrame* childFrame : mColGroups) { if (nsGkAtoms::tableColGroupFrame == childFrame->GetType()) { nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame *)childFrame; colGroupFrame->Dump(1); } } for (colX = 0; colX < numCols; colX++) { printf("\n"); nsTableColFrame* colFrame = GetColFrame(colX); @@ -4402,17 +4392,17 @@ BCMapCellInfo::SetInfo(nsTableRowFrame* mEndRow = mStartRow->GetNextRow(); if (mEndRow) { for (int32_t span = 2; mEndRow && span < mRowSpan; span++) { mEndRow = mEndRow->GetNextRow(); } NS_ASSERTION(mEndRow, "spanned row not found"); } else { - NS_ASSERTION(false, "error in cell map"); + NS_ERROR("error in cell map"); mRowSpan = 1; mEndRow = mStartRow; } } // row group frame info // try to reuse the rgStart and rgEnd from the iterator as calls to // GetRowCount() are computationally expensive and should be avoided if // possible @@ -6991,17 +6981,17 @@ BCVerticalSeg::Paint(BCPaintBorderIterat NS_ERROR("a neighboring rowgroup can never own a vertical border"); // and fall through case eRowGroupOwner: NS_ASSERTION(aIter.IsTableLeftMost() || aIter.IsTableRightMost(), "row group can own border only at table edge"); owner = mFirstRowGroup; break; case eAjaRowOwner: - NS_ASSERTION(false, "program error"); // and fall through + NS_ERROR("program error"); // and fall through case eRowOwner: NS_ASSERTION(aIter.IsTableLeftMost() || aIter.IsTableRightMost(), "row can own border only at table edge"); owner = mFirstRow; break; case eAjaCellOwner: side = eLogicalSideIEnd; cell = mAjaCell; // and fall through
--- a/media/mtransport/databuffer.h +++ b/media/mtransport/databuffer.h @@ -12,38 +12,63 @@ #include <mozilla/UniquePtr.h> #include <m_cpp_utils.h> #include <nsISupportsImpl.h> namespace mozilla { class DataBuffer { public: - DataBuffer() : data_(nullptr), len_(0) {} + DataBuffer() : data_(nullptr), len_(0), capacity_(0) {} DataBuffer(const uint8_t *data, size_t len) { - Assign(data, len); + Assign(data, len, len); + } + DataBuffer(const uint8_t *data, size_t len, size_t capacity) { + Assign(data, len, capacity); + } + + // to ensure extra space for expansion + void Assign(const uint8_t *data, size_t len, size_t capacity) { + MOZ_RELEASE_ASSERT(len <= capacity); + Allocate(capacity); // sets len_ = capacity + memcpy(static_cast<void *>(data_.get()), + static_cast<const void *>(data), len); + len_ = len; } - void Assign(const uint8_t *data, size_t len) { - Allocate(len); - memcpy(static_cast<void *>(data_.get()), - static_cast<const void *>(data), len); + void Allocate(size_t capacity) { + data_.reset(new uint8_t[capacity ? capacity : 1]); // Don't depend on new [0]. + len_ = capacity_ = capacity; } - void Allocate(size_t len) { - data_.reset(new uint8_t[len ? len : 1]); // Don't depend on new [0]. + void EnsureCapacity(size_t capacity) { + if (capacity_ < capacity) { + uint8_t *new_data = new uint8_t[ capacity ? capacity : 1]; + memcpy(static_cast<void *>(new_data), + static_cast<const void *>(data_.get()), len_); + data_.reset(new_data); // after copying! Deletes old data + capacity_ = capacity; + } + } + + // used when something writes to the buffer (having checked + // capacity() or used EnsureCapacity()) and increased the length. + void SetLength(size_t len) { + MOZ_RELEASE_ASSERT(len <= capacity_); len_ = len; } const uint8_t *data() const { return data_.get(); } uint8_t *data() { return data_.get(); } size_t len() const { return len_; } + size_t capacity() const { return capacity_; } private: UniquePtr<uint8_t[]> data_; size_t len_; + size_t capacity_; DISALLOW_COPY_ASSIGN(DataBuffer); }; } #endif
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp @@ -64,17 +64,16 @@ WebrtcAudioConduit::~WebrtcAudioConduit( { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); CSFLogDebug(logTag, "%s ", __FUNCTION__); for(std::vector<AudioCodecConfig*>::size_type i=0;i < mRecvCodecList.size();i++) { delete mRecvCodecList[i]; } - delete mCurSendCodecConfig; // The first one of a pair to be deleted shuts down media for both if(mPtrVoEXmedia) { mPtrVoEXmedia->SetExternalRecordingStatus(false); mPtrVoEXmedia->SetExternalPlayoutStatus(false); } @@ -355,20 +354,22 @@ WebrtcAudioConduit::SetReceiverTransport MediaConduitErrorCode WebrtcAudioConduit::ConfigureSendMediaCodec(const AudioCodecConfig* codecConfig) { CSFLogDebug(logTag, "%s ", __FUNCTION__); MediaConduitErrorCode condError = kMediaConduitNoError; int error = 0;//webrtc engine errors webrtc::CodecInst cinst; - //validate codec param - if((condError = ValidateCodecConfig(codecConfig, true)) != kMediaConduitNoError) { - return condError; + //validate codec param + if((condError = ValidateCodecConfig(codecConfig, true)) != kMediaConduitNoError) + { + return condError; + } } condError = StopTransmitting(); if (condError != kMediaConduitNoError) { return condError; } if(!CodecConfigToWebRTCCodec(codecConfig,cinst)) @@ -406,26 +407,27 @@ WebrtcAudioConduit::ConfigureSendMediaCo } #endif condError = StartTransmitting(); if (condError != kMediaConduitNoError) { return condError; } - //Copy the applied config for future reference. - delete mCurSendCodecConfig; + { + MutexAutoLock lock(mCodecMutex); - mCurSendCodecConfig = new AudioCodecConfig(codecConfig->mType, - codecConfig->mName, - codecConfig->mFreq, - codecConfig->mPacSize, - codecConfig->mChannels, - codecConfig->mRate); - + //Copy the applied config for future reference. + mCurSendCodecConfig = new AudioCodecConfig(codecConfig->mType, + codecConfig->mName, + codecConfig->mFreq, + codecConfig->mPacSize, + codecConfig->mChannels, + codecConfig->mRate); + } return kMediaConduitNoError; } MediaConduitErrorCode WebrtcAudioConduit::ConfigureRecvMediaCodecs( const std::vector<AudioCodecConfig*>& codecConfigList) { CSFLogDebug(logTag, "%s ", __FUNCTION__); @@ -1013,17 +1015,17 @@ WebrtcAudioConduit::CheckCodecForMatch(c /** * Perform validation on the codecConfig to be applied. * Verifies if the codec is already applied. */ MediaConduitErrorCode WebrtcAudioConduit::ValidateCodecConfig(const AudioCodecConfig* codecInfo, - bool send) const + bool send) { bool codecAppliedAlready = false; if(!codecInfo) { CSFLogError(logTag, "%s Null CodecConfig ", __FUNCTION__); return kMediaConduitMalformedArgument; } @@ -1040,16 +1042,18 @@ WebrtcAudioConduit::ValidateCodecConfig( { CSFLogError(logTag, "%s Channel Unsupported ", __FUNCTION__); return kMediaConduitMalformedArgument; } //check if we have the same codec already applied if(send) { + MutexAutoLock lock(mCodecMutex); + codecAppliedAlready = CheckCodecsForMatch(mCurSendCodecConfig,codecInfo); } else { codecAppliedAlready = CheckCodecForMatch(codecInfo); } if(codecAppliedAlready) { CSFLogDebug(logTag, "%s Codec %s Already Applied ", __FUNCTION__, codecInfo->mName.c_str());
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h @@ -164,17 +164,17 @@ public: WebrtcAudioConduit(): mVoiceEngine(nullptr), mTransportMonitor("WebrtcAudioConduit"), mTransmitterTransport(nullptr), mReceiverTransport(nullptr), mEngineTransmitting(false), mEngineReceiving(false), mChannel(-1), - mCurSendCodecConfig(nullptr), + mCodecMutex("AudioConduit codec db"), mCaptureDelay(150), #if !defined(MOZILLA_EXTERNAL_LINKAGE) mLastTimestamp(0), #endif // MOZILLA_INTERNAL_API mSamples(0), mLastSyncLog(0) { } @@ -240,17 +240,17 @@ private: bool CopyCodecToDB(const AudioCodecConfig* codecInfo); // Functions to verify if the codec passed is already in // conduits database bool CheckCodecForMatch(const AudioCodecConfig* codecInfo) const; bool CheckCodecsForMatch(const AudioCodecConfig* curCodecConfig, const AudioCodecConfig* codecInfo) const; //Checks the codec to be applied - MediaConduitErrorCode ValidateCodecConfig(const AudioCodecConfig* codecInfo, bool send) const; + MediaConduitErrorCode ValidateCodecConfig(const AudioCodecConfig* codecInfo, bool send); //Utility function to dump recv codec database void DumpCodecDB() const; webrtc::VoiceEngine* mVoiceEngine; mozilla::ReentrantMonitor mTransportMonitor; mozilla::RefPtr<TransportInterface> mTransmitterTransport; mozilla::RefPtr<TransportInterface> mReceiverTransport; @@ -272,17 +272,19 @@ private: struct Processing { TimeStamp mTimeStamp; uint32_t mRTPTimeStamp; // RTP timestamps received }; nsAutoTArray<Processing,8> mProcessing; int mChannel; RecvCodecList mRecvCodecList; - AudioCodecConfig* mCurSendCodecConfig; + + Mutex mCodecMutex; // protects mCurSendCodecConfig + nsAutoPtr<AudioCodecConfig> mCurSendCodecConfig; // Current "capture" delay (really output plus input delay) int32_t mCaptureDelay; #if !defined(MOZILLA_EXTERNAL_LINKAGE) uint32_t mLastTimestamp; #endif // MOZILLA_INTERNAL_API
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -69,17 +69,17 @@ WebrtcVideoConduit::WebrtcVideoConduit() mTransmitterTransport(nullptr), mReceiverTransport(nullptr), mRenderer(nullptr), mPtrExtCapture(nullptr), mEngineTransmitting(false), mEngineReceiving(false), mChannel(-1), mCapId(-1), - mCurSendCodecConfig(nullptr), + mCodecMutex("VideoConduit codec db"), mSendingWidth(0), mSendingHeight(0), mReceivingWidth(640), mReceivingHeight(480), mSendingFramerate(DEFAULT_VIDEO_MAX_FRAMERATE), mLastFramerateTenths(DEFAULT_VIDEO_MAX_FRAMERATE*10), mNumReceivingStreams(1), mVideoLatencyTestEnable(false), @@ -96,18 +96,16 @@ WebrtcVideoConduit::~WebrtcVideoConduit( NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); CSFLogDebug(logTag, "%s ", __FUNCTION__); for(std::vector<VideoCodecConfig*>::size_type i=0;i < mRecvCodecList.size();i++) { delete mRecvCodecList[i]; } - delete mCurSendCodecConfig; - // The first one of a pair to be deleted shuts down media for both //Deal with External Capturer if(mPtrViECapture) { mPtrViECapture->DisconnectCaptureDevice(mCapId); mPtrViECapture->ReleaseCaptureDevice(mCapId); mPtrExtCapture = nullptr; } @@ -231,18 +229,19 @@ bool WebrtcVideoConduit::GetVideoEncoder // we could use a lock. Note that we don't change it often, and read it once per frame. // We scale by *10 because mozilla::Atomic<> doesn't do 'double' or 'float'. double framerate = mLastFramerateTenths/10.0; // fetch once if (std::abs(*framerateMean - framerate)/framerate > 0.1 && *framerateMean >= 0.5) { // unchanged resolution, but adjust bandwidth limits to match camera fps CSFLogDebug(logTag, "Encoder frame rate changed from %f to %f", (mLastFramerateTenths/10.0), *framerateMean); + MutexAutoLock lock(mCodecMutex); mLastFramerateTenths = *framerateMean * 10; - SelectSendResolution(mSendingWidth, mSendingHeight, true); + SelectSendResolution(mSendingWidth, mSendingHeight); } return true; } bool WebrtcVideoConduit::GetVideoDecoderStats(double* framerateMean, double* framerateStdDev, double* bitrateMean, double* bitrateStdDev, @@ -590,39 +589,41 @@ WebrtcVideoConduit::ConfigureCodecMode(w { CSFLogDebug(logTag, "%s ", __FUNCTION__); mCodecMode = mode; return kMediaConduitNoError; } /** * Note: Setting the send-codec on the Video Engine will restart the encoder, * sets up new SSRC and reset RTP_RTCP module with the new codec setting. + * + * Note: this is called from MainThread, and the codec settings are read on + * videoframe delivery threads (i.e in SendVideoFrame(). With + * renegotiation/reconfiguration, this now needs a lock! Alternatively + * changes could be queued until the next frame is delivered using an + * Atomic pointer and swaps. */ MediaConduitErrorCode WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig) { CSFLogDebug(logTag, "%s for %s", __FUNCTION__, codecConfig ? codecConfig->mName.c_str() : "<null>"); bool codecFound = false; MediaConduitErrorCode condError = kMediaConduitNoError; int error = 0; //webrtc engine errors webrtc::VideoCodec video_codec; std::string payloadName; memset(&video_codec, 0, sizeof(video_codec)); - //validate basic params - if((condError = ValidateCodecConfig(codecConfig,true)) != kMediaConduitNoError) { - return condError; - } - - //Check if we have same codec already applied - if(CheckCodecsForMatch(mCurSendCodecConfig, codecConfig)) - { - CSFLogDebug(logTag, "%s Codec has been applied already ", __FUNCTION__); + //validate basic params + if((condError = ValidateCodecConfig(codecConfig,true)) != kMediaConduitNoError) + { + return condError; + } } condError = StopTransmitting(); if (condError != kMediaConduitNoError) { return condError; } if (mExternalSendCodec && @@ -704,20 +705,22 @@ WebrtcVideoConduit::ConfigureSendMediaCo } } condError = StartTransmitting(); if (condError != kMediaConduitNoError) { return condError; } - //Copy the applied config for future reference. - delete mCurSendCodecConfig; + { + MutexAutoLock lock(mCodecMutex); - mCurSendCodecConfig = new VideoCodecConfig(*codecConfig); + //Copy the applied config for future reference. + mCurSendCodecConfig = new VideoCodecConfig(*codecConfig); + } mPtrRTP->SetRembStatus(mChannel, true, false); return kMediaConduitNoError; } MediaConduitErrorCode WebrtcVideoConduit::ConfigureRecvMediaCodecs( @@ -992,21 +995,22 @@ WebrtcVideoConduit::SelectBandwidth(webr // Mostly this would be ultra-low-light situations/mobile or screensharing. vie_codec.minBitrate = vie_codec.minBitrate * ((10-(framerate/2))/30); vie_codec.maxBitrate = vie_codec.maxBitrate * ((10-(framerate/2))/30); } } // XXX we need to figure out how to feed back changes in preferred capture // resolution to the getUserMedia source +// Invoked under lock of mCodecMutex! bool WebrtcVideoConduit::SelectSendResolution(unsigned short width, - unsigned short height, - bool force) + unsigned short height) { + mCodecMutex.AssertCurrentThreadOwns(); // XXX This will do bandwidth-resolution adaptation as well - bug 877954 // Limit resolution to max-fs while keeping same aspect ratio as the // incoming image. if (mCurSendCodecConfig && mCurSendCodecConfig->mMaxFrameSize) { unsigned int cur_fs, max_width, max_height, mb_width, mb_height, mb_max; @@ -1066,102 +1070,94 @@ WebrtcVideoConduit::SelectSendResolution // Favor even multiples of pixels for width and height. width = std::max(width & ~1, 2); height = std::max(height & ~1, 2); } // Adapt to getUserMedia resolution changes // check if we need to reconfigure the sending resolution. - // force tells us to do it regardless, such as when the FPS changes - if (mSendingWidth != width || mSendingHeight != height || force) + bool changed = false; + if (mSendingWidth != width || mSendingHeight != height) { // This will avoid us continually retrying this operation if it fails. // If the resolution changes, we'll try again. In the meantime, we'll // keep using the old size in the encoder. mSendingWidth = width; mSendingHeight = height; + changed = true; + } + // uses mSendingWidth/Height + unsigned int framerate = SelectSendFrameRate(mSendingFramerate); + if (mSendingFramerate != framerate) { + mSendingFramerate = framerate; + changed = true; + } + + if (changed) { // Get current vie codec. webrtc::VideoCodec vie_codec; int32_t err; if ((err = mPtrViECodec->GetSendCodec(mChannel, vie_codec)) != 0) { CSFLogError(logTag, "%s: GetSendCodec failed, err %d", __FUNCTION__, err); return false; } - if (vie_codec.width != width || vie_codec.height != height || force) + // Likely spurious unless there was some error, but rarely checked + if (vie_codec.width != width || vie_codec.height != height || + vie_codec.maxFramerate != mSendingFramerate) { vie_codec.width = width; vie_codec.height = height; + vie_codec.maxFramerate = mSendingFramerate; SelectBandwidth(vie_codec, width, height); if ((err = mPtrViECodec->SetSendCodec(mChannel, vie_codec)) != 0) { CSFLogError(logTag, "%s: SetSendCodec(%ux%u) failed, err %d", __FUNCTION__, width, height, err); return false; } - CSFLogDebug(logTag, "%s: Encoder resolution changed to %ux%u, bitrate %u:%u", - __FUNCTION__, width, height, + CSFLogDebug(logTag, "%s: Encoder resolution changed to %ux%u @ %ufps, bitrate %u:%u", + __FUNCTION__, width, height, mSendingFramerate, vie_codec.minBitrate, vie_codec.maxBitrate); } // else no change; mSendingWidth likely was 0 } return true; } -// TODO(ruil2@cisco.com):combine SelectSendResolution with SelectSendFrameRate Bug 1132318 -bool -WebrtcVideoConduit::SelectSendFrameRate(unsigned int framerate) + +// Invoked under lock of mCodecMutex! +unsigned int +WebrtcVideoConduit::SelectSendFrameRate(unsigned int framerate) const { + mCodecMutex.AssertCurrentThreadOwns(); + unsigned int new_framerate = framerate; + // Limit frame rate based on max-mbps - mSendingFramerate = framerate; if (mCurSendCodecConfig && mCurSendCodecConfig->mMaxMBPS) { unsigned int cur_fs, mb_width, mb_height, max_fps; mb_width = (mSendingWidth + 15) >> 4; mb_height = (mSendingHeight + 15) >> 4; cur_fs = mb_width * mb_height; max_fps = mCurSendCodecConfig->mMaxMBPS/cur_fs; if (max_fps < mSendingFramerate) { - mSendingFramerate = max_fps; + new_framerate = max_fps; } if (mCurSendCodecConfig->mMaxFrameRate != 0 && mCurSendCodecConfig->mMaxFrameRate < mSendingFramerate) { - mSendingFramerate = mCurSendCodecConfig->mMaxFrameRate; + new_framerate = mCurSendCodecConfig->mMaxFrameRate; } } - if (mSendingFramerate != framerate) - { - // Get current vie codec. - webrtc::VideoCodec vie_codec; - int32_t err; - - if ((err = mPtrViECodec->GetSendCodec(mChannel, vie_codec)) != 0) - { - CSFLogError(logTag, "%s: GetSendCodec failed, err %d", __FUNCTION__, err); - return false; - } - if (vie_codec.maxFramerate != mSendingFramerate) - { - vie_codec.maxFramerate = mSendingFramerate; - if ((err = mPtrViECodec->SetSendCodec(mChannel, vie_codec)) != 0) - { - CSFLogError(logTag, "%s: SetSendCodec(%u) failed, err %d", - __FUNCTION__, mSendingFramerate, err); - return false; - } - CSFLogDebug(logTag, "%s: Encoder framerate changed to %u", - __FUNCTION__, mSendingFramerate); - } - } - return true; + return new_framerate; } MediaConduitErrorCode WebrtcVideoConduit::SetExternalSendCodec(VideoCodecConfig* config, VideoEncoder* encoder) { if (!mPtrExtCodec->RegisterExternalSendCodec(mChannel, config->mType, static_cast<WebrtcVideoEncoder*>(encoder), @@ -1216,23 +1212,22 @@ WebrtcVideoConduit::SendVideoFrame(unsig // Transmission should be enabled before we insert any frames. if(!mEngineTransmitting) { CSFLogError(logTag, "%s Engine not transmitting ", __FUNCTION__); return kMediaConduitSessionNotInited; } - if (!SelectSendResolution(width, height, false)) { - return kMediaConduitCaptureError; - } - if (!SelectSendFrameRate(mSendingFramerate)) - { - return kMediaConduitCaptureError; + MutexAutoLock lock(mCodecMutex); + if (!SelectSendResolution(width, height)) + { + return kMediaConduitCaptureError; + } } // insert the frame to video engine in I420 format only MOZ_ASSERT(mPtrExtCapture); if(mPtrExtCapture->IncomingFrame(video_frame, video_frame_length, width, height, type, (unsigned long long)capture_time) == -1) @@ -1604,17 +1599,17 @@ WebrtcVideoConduit::CheckCodecForMatch(c } /** * Perform validation on the codecConfig to be applied * Verifies if the codec is already applied. */ MediaConduitErrorCode WebrtcVideoConduit::ValidateCodecConfig(const VideoCodecConfig* codecInfo, - bool send) const + bool send) { bool codecAppliedAlready = false; if(!codecInfo) { CSFLogError(logTag, "%s Null CodecConfig ", __FUNCTION__); return kMediaConduitMalformedArgument; } @@ -1624,16 +1619,18 @@ WebrtcVideoConduit::ValidateCodecConfig( { CSFLogError(logTag, "%s Invalid Payload Name Length ", __FUNCTION__); return kMediaConduitMalformedArgument; } //check if we have the same codec already applied if(send) { + MutexAutoLock lock(mCodecMutex); + codecAppliedAlready = CheckCodecsForMatch(mCurSendCodecConfig,codecInfo); } else { codecAppliedAlready = CheckCodecForMatch(codecInfo); } if(codecAppliedAlready) { CSFLogDebug(logTag, "%s Codec %s Already Applied ", __FUNCTION__, codecInfo->mName.c_str());
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h @@ -140,25 +140,25 @@ public: unsigned short height); /** * Function to select and change the encoding resolution based on incoming frame size * and current available bandwidth. * @param width, height: dimensions of the frame * @param force: force setting the codec config if framerate may require a bandwidth change */ bool SelectSendResolution(unsigned short width, - unsigned short height, - bool force); + unsigned short height); /** * Function to select and change the encoding frame rate based on incoming frame rate * and max-mbps setting. - * @param framerate + * @param current framerate + * @result new framerate */ - bool SelectSendFrameRate(unsigned int framerate); + unsigned int SelectSendFrameRate(unsigned int framerate) const; /** * Function to deliver a capture video frame for encoding and transport * @param video_frame: pointer to captured video-frame. * @param video_frame_length: size of the frame * @param width, height: dimensions of the frame * @param video_type: Type of the video frame - I420, RAW * @param captured_time: timestamp when the frame was captured. @@ -301,17 +301,17 @@ private: // Functions to verify if the codec passed is already in // conduits database bool CheckCodecForMatch(const VideoCodecConfig* codecInfo) const; bool CheckCodecsForMatch(const VideoCodecConfig* curCodecConfig, const VideoCodecConfig* codecInfo) const; //Checks the codec to be applied - MediaConduitErrorCode ValidateCodecConfig(const VideoCodecConfig* codecInfo, bool send) const; + MediaConduitErrorCode ValidateCodecConfig(const VideoCodecConfig* codecInfo, bool send); //Utility function to dump recv codec database void DumpCodecDB() const; // Video Latency Test averaging filter void VideoLatencyUpdate(uint64_t new_sample); webrtc::VideoEngine* mVideoEngine; @@ -332,17 +332,20 @@ private: // Engine state we are concerned with. mozilla::Atomic<bool> mEngineTransmitting; //If true ==> Transmit Sub-system is up and running mozilla::Atomic<bool> mEngineReceiving; // if true ==> Receive Sus-sysmtem up and running int mChannel; // Video Channel for this conduit int mCapId; // Capturer for this conduit RecvCodecList mRecvCodecList; - VideoCodecConfig* mCurSendCodecConfig; + + Mutex mCodecMutex; // protects mCurrSendCodecConfig + nsAutoPtr<VideoCodecConfig> mCurSendCodecConfig; + unsigned short mSendingWidth; unsigned short mSendingHeight; unsigned short mReceivingWidth; unsigned short mReceivingHeight; unsigned int mSendingFramerate; // scaled by *10 because Atomic<double/float> isn't supported mozilla::Atomic<int32_t, mozilla::Relaxed> mLastFramerateTenths; unsigned short mNumReceivingStreams;
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -745,119 +745,95 @@ MediaPipeline::TransportInfo* MediaPipel return nullptr; } nsresult MediaPipeline::PipelineTransport::SendRtpPacket( const void *data, int len) { nsAutoPtr<DataBuffer> buf(new DataBuffer(static_cast<const uint8_t *>(data), - len)); + len, len + SRTP_MAX_EXPANSION)); RUN_ON_THREAD(sts_thread_, WrapRunnable( RefPtr<MediaPipeline::PipelineTransport>(this), - &MediaPipeline::PipelineTransport::SendRtpPacket_s, - buf), + &MediaPipeline::PipelineTransport::SendRtpRtcpPacket_s, + buf, true), NS_DISPATCH_NORMAL); return NS_OK; } -nsresult MediaPipeline::PipelineTransport::SendRtpPacket_s( - nsAutoPtr<DataBuffer> data) { +nsresult MediaPipeline::PipelineTransport::SendRtpRtcpPacket_s( + nsAutoPtr<DataBuffer> data, + bool is_rtp) { + ASSERT_ON_THREAD(sts_thread_); - if (!pipeline_) + if (!pipeline_) { return NS_OK; // Detached + } + TransportInfo& transport = is_rtp ? pipeline_->rtp_ : pipeline_->rtcp_; - if (!pipeline_->rtp_.send_srtp_) { - MOZ_MTLOG(ML_DEBUG, "Couldn't write RTP packet; SRTP not set up yet"); + if (!transport.send_srtp_) { + MOZ_MTLOG(ML_DEBUG, "Couldn't write RTP/RTCP packet; SRTP not set up yet"); return NS_OK; } - MOZ_ASSERT(pipeline_->rtp_.transport_); - NS_ENSURE_TRUE(pipeline_->rtp_.transport_, NS_ERROR_NULL_POINTER); + MOZ_ASSERT(transport.transport_); + NS_ENSURE_TRUE(transport.transport_, NS_ERROR_NULL_POINTER); - // libsrtp enciphers in place, so we need a new, big enough - // buffer. - // XXX. allocates and deletes one buffer per packet sent. - // Bug 822129 - int max_len = data->len() + SRTP_MAX_EXPANSION; - ScopedDeletePtr<unsigned char> inner_data( - new unsigned char[max_len]); - memcpy(inner_data, data->data(), data->len()); + // libsrtp enciphers in place, so we need a big enough buffer. + MOZ_ASSERT(data->capacity() >= data->len() + SRTP_MAX_EXPANSION); int out_len; - nsresult res = pipeline_->rtp_.send_srtp_->ProtectRtp(inner_data, - data->len(), - max_len, - &out_len); - if (!NS_SUCCEEDED(res)) + nsresult res; + if (is_rtp) { + res = transport.send_srtp_->ProtectRtp(data->data(), + data->len(), + data->capacity(), + &out_len); + } else { + res = transport.send_srtp_->ProtectRtcp(data->data(), + data->len(), + data->capacity(), + &out_len); + } + if (!NS_SUCCEEDED(res)) { return res; + } - MOZ_MTLOG(ML_DEBUG, pipeline_->description_ << " sending RTP packet."); - pipeline_->increment_rtp_packets_sent(out_len); - return pipeline_->SendPacket(pipeline_->rtp_.transport_, inner_data, - out_len); + // paranoia; don't have uninitialized bytes included in data->len() + data->SetLength(out_len); + + MOZ_MTLOG(ML_DEBUG, pipeline_->description_ << " sending " << + (is_rtp ? "RTP" : "RTCP") << " packet"); + if (is_rtp) { + pipeline_->increment_rtp_packets_sent(out_len); + } else { + pipeline_->increment_rtcp_packets_sent(); + } + return pipeline_->SendPacket(transport.transport_, data->data(), out_len); } nsresult MediaPipeline::PipelineTransport::SendRtcpPacket( const void *data, int len) { nsAutoPtr<DataBuffer> buf(new DataBuffer(static_cast<const uint8_t *>(data), - len)); + len, len + SRTP_MAX_EXPANSION)); RUN_ON_THREAD(sts_thread_, WrapRunnable( RefPtr<MediaPipeline::PipelineTransport>(this), - &MediaPipeline::PipelineTransport::SendRtcpPacket_s, - buf), + &MediaPipeline::PipelineTransport::SendRtpRtcpPacket_s, + buf, false), NS_DISPATCH_NORMAL); return NS_OK; } -nsresult MediaPipeline::PipelineTransport::SendRtcpPacket_s( - nsAutoPtr<DataBuffer> data) { - ASSERT_ON_THREAD(sts_thread_); - if (!pipeline_) - return NS_OK; // Detached - - if (!pipeline_->rtcp_.send_srtp_) { - MOZ_MTLOG(ML_DEBUG, "Couldn't write RTCP packet; SRTCP not set up yet"); - return NS_OK; - } - - MOZ_ASSERT(pipeline_->rtcp_.transport_); - NS_ENSURE_TRUE(pipeline_->rtcp_.transport_, NS_ERROR_NULL_POINTER); - - // libsrtp enciphers in place, so we need a new, big enough - // buffer. - // XXX. allocates and deletes one buffer per packet sent. - // Bug 822129. - int max_len = data->len() + SRTP_MAX_EXPANSION; - ScopedDeletePtr<unsigned char> inner_data( - new unsigned char[max_len]); - memcpy(inner_data, data->data(), data->len()); - - int out_len; - nsresult res = pipeline_->rtcp_.send_srtp_->ProtectRtcp(inner_data, - data->len(), - max_len, - &out_len); - - if (!NS_SUCCEEDED(res)) - return res; - - MOZ_MTLOG(ML_DEBUG, pipeline_->description_ << " sending RTCP packet."); - pipeline_->increment_rtcp_packets_sent(); - return pipeline_->SendPacket(pipeline_->rtcp_.transport_, inner_data, - out_len); -} - void MediaPipelineTransmit::PipelineListener:: UnsetTrackId(MediaStreamGraphImpl* graph) { #ifndef USE_FAKE_MEDIA_STREAMS class Message : public ControlMessage { public: explicit Message(PipelineListener* listener) : ControlMessage(nullptr), listener_(listener) {} virtual void Run() override
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h @@ -186,18 +186,18 @@ class MediaPipeline : public sigslot::ha void Detach() { pipeline_ = nullptr; } MediaPipeline *pipeline() const { return pipeline_; } virtual nsresult SendRtpPacket(const void* data, int len); virtual nsresult SendRtcpPacket(const void* data, int len); private: - virtual nsresult SendRtpPacket_s(nsAutoPtr<DataBuffer> data); - virtual nsresult SendRtcpPacket_s(nsAutoPtr<DataBuffer> data); + nsresult SendRtpRtcpPacket_s(nsAutoPtr<DataBuffer> data, + bool is_rtp); MediaPipeline *pipeline_; // Raw pointer to avoid cycles nsCOMPtr<nsIEventTarget> sts_thread_; }; friend class PipelineTransport; class TransportInfo { public:
--- a/media/webrtc/signaling/src/mediapipeline/SrtpFlow.h +++ b/media/webrtc/signaling/src/mediapipeline/SrtpFlow.h @@ -17,19 +17,19 @@ typedef struct srtp_ctx_t *srtp_t; typedef struct srtp_event_data_t srtp_event_data_t; namespace mozilla { #define SRTP_MASTER_KEY_LENGTH 16 #define SRTP_MASTER_SALT_LENGTH 14 #define SRTP_TOTAL_KEY_LENGTH (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH) -// For some reason libsrtp increases packet size by > 12 for RTCP even though -// the doc claims otherwise. -#define SRTP_MAX_EXPANSION 20 +// SRTCP requires an auth tag *plus* a 4-byte index-plus-'E'-bit value (see +// RFC 3711) +#define SRTP_MAX_EXPANSION (SRTP_MAX_TRAILER_LEN+4) class SrtpFlow { ~SrtpFlow(); public: static mozilla::RefPtr<SrtpFlow> Create(int cipher_suite,
--- a/memory/replace/dmd/DMD.cpp +++ b/memory/replace/dmd/DMD.cpp @@ -365,16 +365,18 @@ class Options public: explicit Options(const char* aDMDEnvVar); bool IsLiveMode() const { return mMode == Live; } bool IsDarkMatterMode() const { return mMode == DarkMatter; } bool IsCumulativeMode() const { return mMode == Cumulative; } + const char* ModeString() const; + const char* DMDEnvVar() const { return mDMDEnvVar; } size_t SampleBelowSize() const { return mSampleBelowSize.mActual; } size_t MaxFrames() const { return mMaxFrames.mActual; } size_t ShowDumpStats() const { return mShowDumpStats; } }; static Options *gOptions; @@ -1493,16 +1495,32 @@ Options::BadArg(const char* aArg) StatusMsg(" --max-frames=<1..%d> Max. depth of stack traces [%d]\n", int(mMaxFrames.mMax), int(mMaxFrames.mDefault)); StatusMsg(" --show-dump-stats=<yes|no> Show stats about dumps? [no]\n"); StatusMsg("\n"); exit(1); } +const char* +Options::ModeString() const +{ + switch (mMode) { + case Live: + return "live"; + case DarkMatter: + return "dark-matter"; + case Cumulative: + return "cumulative"; + default: + MOZ_ASSERT(false); + return "(unknown DMD mode)"; + } +} + //--------------------------------------------------------------------------- // DMD start-up //--------------------------------------------------------------------------- #ifdef XP_MACOSX static void NopStackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure) @@ -1775,29 +1793,17 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr { const char* var = gOptions->DMDEnvVar(); if (var) { writer.StringProperty("dmdEnvVar", var); } else { writer.NullProperty("dmdEnvVar"); } - const char* mode; - if (gOptions->IsLiveMode()) { - mode = "live"; - } else if (gOptions->IsDarkMatterMode()) { - mode = "dark-matter"; - } else if (gOptions->IsCumulativeMode()) { - mode = "cumulative"; - } else { - MOZ_ASSERT(false); - mode = "(unknown DMD mode)"; - } - writer.StringProperty("mode", mode); - + writer.StringProperty("mode", gOptions->ModeString()); writer.IntProperty("sampleBelowSize", gOptions->SampleBelowSize()); } writer.EndObject(); StatusMsg(" Constructing the heap block list...\n"); ToIdStringConverter isc; @@ -1813,17 +1819,16 @@ AnalyzeImpl(UniquePtr<JSONWriteFunc> aWr if (!b.IsSampled()) { writer.IntProperty("req", b.ReqSize()); if (b.SlopSize() > 0) { writer.IntProperty("slop", b.SlopSize()); } } writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace())); if (gOptions->IsDarkMatterMode() && b.NumReports() > 0) { - MOZ_ASSERT(gOptions->IsDarkMatterMode()); writer.StartArrayProperty("reps"); { if (b.ReportStackTrace1()) { writer.StringElement(isc.ToIdString(b.ReportStackTrace1())); } if (b.ReportStackTrace2()) { writer.StringElement(isc.ToIdString(b.ReportStackTrace2())); }
--- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -911,8 +911,13 @@ pref("touchcaret.extendedvisibility", tr pref("caret.manages-android-actionbar", true); // Disable sending console to logcat on release builds. #ifdef RELEASE_BUILD pref("consoleservice.logcat", false); #else pref("consoleservice.logcat", true); #endif + +// Enable Service Workers for Android on non-release builds +#ifndef RELEASE_BUILD +pref("dom.serviceWorkers.enabled", true); +#endif
--- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -690,16 +690,26 @@ nsJARChannel::OverrideSecurityInfo(nsISu MOZ_RELEASE_ASSERT(aSecurityInfo, "This can only be called with a valid security info object"); MOZ_RELEASE_ASSERT(ShouldIntercept(), "This can only be called on channels that can be intercepted"); mSecurityInfo = aSecurityInfo; return NS_OK; } +void +nsJARChannel::OverrideURI(nsIURI* aRedirectedURI) +{ + MOZ_RELEASE_ASSERT(mLoadFlags & LOAD_REPLACE, + "This can only happen if the LOAD_REPLACE flag is set"); + MOZ_RELEASE_ASSERT(ShouldIntercept(), + "This can only be called on channels that can be intercepted"); + mAppURI = aRedirectedURI; +} + NS_IMETHODIMP nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo) { NS_PRECONDITION(aSecurityInfo, "Null out param"); NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); return NS_OK; }
--- a/modules/libjar/nsJARChannel.h +++ b/modules/libjar/nsJARChannel.h @@ -57,16 +57,17 @@ public: NS_DECL_NSITHREADRETARGETABLEREQUEST NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER nsJARChannel(); nsresult Init(nsIURI *uri); nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo); + void OverrideURI(nsIURI* aRedirectedURI); private: virtual ~nsJARChannel(); nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **); nsresult LookupFile(bool aAllowAsync); nsresult OpenLocalFile(); void NotifyError(nsresult aError);
--- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -1431,16 +1431,26 @@ HttpBaseChannel::OverrideSecurityInfo(ns MOZ_RELEASE_ASSERT(aSecurityInfo, "This can only be called with a valid security info object"); MOZ_RELEASE_ASSERT(ShouldIntercept(), "This can only be called on channels that can be intercepted"); mSecurityInfo = aSecurityInfo; return NS_OK; } +void +HttpBaseChannel::OverrideURI(nsIURI* aRedirectedURI) +{ + MOZ_RELEASE_ASSERT(mLoadFlags & LOAD_REPLACE, + "This can only happen if the LOAD_REPLACE flag is set"); + MOZ_RELEASE_ASSERT(ShouldIntercept(), + "This can only be called on channels that can be intercepted"); + mURI = aRedirectedURI; +} + NS_IMETHODIMP HttpBaseChannel::IsNoStoreResponse(bool *value) { if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE; *value = mResponseHead->NoStore(); return NS_OK; }
--- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -244,16 +244,17 @@ public: nsHttpResponseHead * GetResponseHead() const { return mResponseHead; } nsHttpRequestHead * GetRequestHead() { return &mRequestHead; } const NetAddr& GetSelfAddr() { return mSelfAddr; } const NetAddr& GetPeerAddr() { return mPeerAddr; } nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo); + void OverrideURI(nsIURI* aRedirectedURI); public: /* Necko internal use only... */ bool IsNavigation(); // Return whether upon a redirect code of httpStatus for method, the // request method should be rewritten to GET. static bool ShouldRewriteRedirectToGET(uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method);
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -2726,17 +2726,25 @@ nsHttpChannel::OpenCacheEntry(bool isHtt nsCOMPtr<nsICacheStorage> cacheStorage; nsCOMPtr<nsIURI> openURI; if (!mFallbackKey.IsEmpty() && mFallbackChannel) { // This is a fallback channel, open fallback URI instead rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey); NS_ENSURE_SUCCESS(rv, rv); } else { - openURI = mURI; + // In the case of intercepted channels, we need to construct the cache + // entry key based on the original URI, so that in case the intercepted + // channel is redirected, the cache entry key before and after the + // redirect is the same. + if (PossiblyIntercepted()) { + openURI = mOriginalURI; + } else { + openURI = mURI; + } } uint32_t appId = info->AppId(); bool appOffline = false; if (appId != NECKO_NO_APP_ID) { gIOService->IsAppOffline(appId, &appOffline); LOG(("nsHttpChannel::OpenCacheEntry appId: %u, offline: %d\n", appId, appOffline)); @@ -2804,16 +2812,21 @@ nsHttpChannel::OpenCacheEntry(bool isHtt nsCOMPtr<nsINetworkInterceptController> controller; GetCallback(controller); nsRefPtr<InterceptedChannelChrome> intercepted = new InterceptedChannelChrome(this, controller, entry); intercepted->NotifyController(); } else { + if (mInterceptCache == INTERCEPTED) { + DebugOnly<bool> exists; + MOZ_ASSERT(NS_SUCCEEDED(cacheStorage->Exists(openURI, extension, &exists)) && exists, + "The entry must exist in the cache after we create it here"); + } rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, this); NS_ENSURE_SUCCESS(rv, rv); } waitFlags.Keep(WAIT_FOR_CACHE_ENTRY); bypassCacheEntryOpen: if (!mApplicationCacheForWrite)
--- a/security/sandbox/linux/Sandbox.cpp +++ b/security/sandbox/linux/Sandbox.cpp @@ -28,17 +28,21 @@ #include <sys/syscall.h> #include <sys/time.h> #include <unistd.h> #include "mozilla/Atomics.h" #include "mozilla/SandboxInfo.h" #include "mozilla/UniquePtr.h" #include "mozilla/unused.h" +#include "sandbox/linux/bpf_dsl/dump_bpf.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" +#include "sandbox/linux/seccomp-bpf/trap.h" #if defined(ANDROID) #include "sandbox/linux/services/android_ucontext.h" #endif #include "sandbox/linux/services/linux_syscalls.h" #ifdef MOZ_ASAN // Copy libsanitizer declarations to avoid depending on ASAN headers. // See also bug 1081242 comment #4. @@ -64,136 +68,130 @@ namespace mozilla { #ifdef ANDROID SandboxCrashFunc gSandboxCrashFunc; #endif #ifdef MOZ_GMP_SANDBOX // For media plugins, we can start the sandbox before we dlopen the // module, so we have to pre-open the file and simulate the sandboxed // open(). -static int gMediaPluginFileDesc = -1; -static const char *gMediaPluginFilePath; +static SandboxOpenedFile gMediaPluginFile; #endif static UniquePtr<SandboxChroot> gChrootHelper; +static void (*gChromiumSigSysHandler)(int, siginfo_t*, void*); + +// Test whether a ucontext, interpreted as the state after a syscall, +// indicates the given error. See also sandbox::Syscall::PutValueInUcontext. +static bool +ContextIsError(const ucontext_t *aContext, int aError) +{ + // Avoid integer promotion warnings. (The unary addition makes + // the decltype not evaluate to a reference type.) + typedef decltype(+SECCOMP_RESULT(aContext)) reg_t; + +#ifdef __mips__ + return SECCOMP_PARM4(aContext) != 0 + && SECCOMP_RESULT(aContext) == static_cast<reg_t>(aError); +#else + return SECCOMP_RESULT(aContext) == static_cast<reg_t>(-aError); +#endif +} /** - * This is the SIGSYS handler function. It is used to report to the user - * which system call has been denied by Seccomp. - * This function also makes the process exit as denying the system call - * will otherwise generally lead to unexpected behavior from the process, - * since we don't know if all functions will handle such denials gracefully. + * This is the SIGSYS handler function. It delegates to the Chromium + * TrapRegistry handler (see InstallSigSysHandler, below) and, if the + * trap handler installed by the policy would fail with ENOSYS, + * crashes the process. This allows unintentional policy failures to + * be reported as crash dumps and fixed. It also logs information + * about the failed system call. * - * @see InstallSyscallReporter() function. + * Note that this could be invoked in parallel on multiple threads and + * that it could be in async signal context (e.g., intercepting an + * open() called from an async signal handler). */ static void -Reporter(int nr, siginfo_t *info, void *void_context) +SigSysHandler(int nr, siginfo_t *info, void *void_context) { ucontext_t *ctx = static_cast<ucontext_t*>(void_context); - unsigned long syscall_nr, args[6]; - pid_t pid = getpid(); - - if (nr != SIGSYS) { - return; - } - if (info->si_code != SYS_SECCOMP) { - return; - } + // This shouldn't ever be null, but the Chromium handler checks for + // that and refrains from crashing, so let's not crash release builds: + MOZ_DIAGNOSTIC_ASSERT(ctx); if (!ctx) { return; } - syscall_nr = SECCOMP_SYSCALL(ctx); - args[0] = SECCOMP_PARM1(ctx); - args[1] = SECCOMP_PARM2(ctx); - args[2] = SECCOMP_PARM3(ctx); - args[3] = SECCOMP_PARM4(ctx); - args[4] = SECCOMP_PARM5(ctx); - args[5] = SECCOMP_PARM6(ctx); + // Save a copy of the context before invoking the trap handler, + // which will overwrite one or more registers with the return value. + ucontext_t savedCtx = *ctx; -#if defined(ANDROID) && ANDROID_VERSION < 16 - // Bug 1093893: Translate tkill to tgkill for pthread_kill; fixed in - // bionic commit 10c8ce59a (in JB and up; API level 16 = Android 4.1). - if (syscall_nr == __NR_tkill) { - intptr_t ret = syscall(__NR_tgkill, getpid(), args[0], args[1]); - if (ret < 0) { - ret = -errno; - } - SECCOMP_RESULT(ctx) = ret; + gChromiumSigSysHandler(nr, info, ctx); + if (!ContextIsError(ctx, ENOSYS)) { return; } -#endif - -#ifdef MOZ_GMP_SANDBOX - if (syscall_nr == __NR_open && gMediaPluginFilePath) { - const char *path = reinterpret_cast<const char*>(args[0]); - int flags = int(args[1]); - if ((flags & O_ACCMODE) != O_RDONLY) { - SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)", - path, flags); - } else if (strcmp(path, gMediaPluginFilePath) != 0) { - SANDBOX_LOG_ERROR("attempt to open file %s which is not the media plugin" - " %s", path, gMediaPluginFilePath); - } else if (gMediaPluginFileDesc == -1) { - SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented"); - } else { - SECCOMP_RESULT(ctx) = gMediaPluginFileDesc; - gMediaPluginFileDesc = -1; - return; - } - } -#endif + pid_t pid = getpid(); + unsigned long syscall_nr = SECCOMP_SYSCALL(&savedCtx); + unsigned long args[6]; + args[0] = SECCOMP_PARM1(&savedCtx); + args[1] = SECCOMP_PARM2(&savedCtx); + args[2] = SECCOMP_PARM3(&savedCtx); + args[3] = SECCOMP_PARM4(&savedCtx); + args[4] = SECCOMP_PARM5(&savedCtx); + args[5] = SECCOMP_PARM6(&savedCtx); + // TODO, someday when this is enabled on MIPS: include the two extra + // args in the error message. SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, syscall %lu," " args %lu %lu %lu %lu %lu %lu. Killing process.", pid, syscall_nr, args[0], args[1], args[2], args[3], args[4], args[5]); // Bug 1017393: record syscall number somewhere useful. info->si_addr = reinterpret_cast<void*>(syscall_nr); - gSandboxCrashFunc(nr, info, void_context); + gSandboxCrashFunc(nr, info, &savedCtx); _exit(127); } /** - * The reporter is called when the process receives a SIGSYS signal. - * The signal is sent by the kernel when Seccomp encounter a system call - * that has not been allowed. - * We register an action for that signal (calling the Reporter function). - * - * This function should not be used in production and thus generally be - * called from debug code. In production, the process is directly killed. - * For this reason, the function is ifdef'd, as there is no reason to - * compile it while unused. - * - * @return 0 on success, -1 on failure. - * @see Reporter() function. + * This function installs the SIGSYS handler. This is slightly + * complicated because we want to use Chromium's handler to dispatch + * to specific trap handlers defined in the policy, but we also need + * the full original signal context to give to Breakpad for crash + * dumps. So we install Chromium's handler first, then retrieve its + * address so our replacement can delegate to it. */ -static int -InstallSyscallReporter(void) +static void +InstallSigSysHandler(void) { struct sigaction act; - sigset_t mask; - memset(&act, 0, sizeof(act)); - sigemptyset(&mask); - sigaddset(&mask, SIGSYS); + + // Ensure that the Chromium handler is installed. + unused << sandbox::Trap::Registry(); + + // If the signal handling state isn't as expected, crash now instead + // of crashing later (and more confusingly) when SIGSYS happens. - act.sa_sigaction = &Reporter; - act.sa_flags = SA_SIGINFO | SA_NODEFER; + if (sigaction(SIGSYS, nullptr, &act) != 0) { + MOZ_CRASH("Couldn't read old SIGSYS disposition"); + } + if ((act.sa_flags & SA_SIGINFO) != SA_SIGINFO) { + MOZ_CRASH("SIGSYS not already set to a siginfo handler?"); + } + MOZ_RELEASE_ASSERT(act.sa_sigaction); + gChromiumSigSysHandler = act.sa_sigaction; + act.sa_sigaction = SigSysHandler; + // Currently, SA_NODEFER should already be set by the Chromium code, + // but it's harmless to ensure that it's set: + MOZ_ASSERT(act.sa_flags & SA_NODEFER); + act.sa_flags |= SA_NODEFER; if (sigaction(SIGSYS, &act, nullptr) < 0) { - return -1; + MOZ_CRASH("Couldn't change SIGSYS disposition"); } - if (sigemptyset(&mask) || - sigaddset(&mask, SIGSYS) || - sigprocmask(SIG_UNBLOCK, &mask, nullptr)) { - return -1; - } - return 0; } /** * This function installs the syscall filter, a.k.a. seccomp. * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more * syscalls to the process beyond this point (even after fork()). * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows * to pass a bpf program (in our case, it contains a syscall @@ -215,19 +213,19 @@ InstallSyscallFilter(const sock_fprog *p SANDBOX_LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s", strerror(errno)); MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)"); } } // Use signals for permissions that need to be set per-thread. // The communication channel from the signal handler back to the main thread. -static mozilla::Atomic<int> sSetSandboxDone; +static mozilla::Atomic<int> gSetSandboxDone; // Pass the filter itself through a global. -static const sock_fprog *sSetSandboxFilter; +static sock_fprog gSetSandboxFilter; // We have to dynamically allocate the signal number; see bug 1038900. // This function returns the first realtime signal currently set to // default handling (i.e., not in use), or 0 if none could be found. // // WARNING: if this function or anything similar to it (including in // external libraries) is used on multiple threads concurrently, there // will be a race condition. @@ -247,47 +245,52 @@ FindFreeSignalNumber() } // Returns true if sandboxing was enabled, or false if sandboxing // already was enabled. Crashes if sandboxing could not be enabled. static bool SetThreadSandbox() { if (prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) { - InstallSyscallFilter(sSetSandboxFilter); + InstallSyscallFilter(&gSetSandboxFilter); return true; } return false; } static void SetThreadSandboxHandler(int signum) { // The non-zero number sent back to the main thread indicates // whether action was taken. if (SetThreadSandbox()) { - sSetSandboxDone = 2; + gSetSandboxDone = 2; } else { - sSetSandboxDone = 1; + gSetSandboxDone = 1; } // Wake up the main thread. See the FUTEX_WAIT call, below, for an // explanation. - syscall(__NR_futex, reinterpret_cast<int*>(&sSetSandboxDone), + syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone), FUTEX_WAKE, 1); } static void -BroadcastSetThreadSandbox(SandboxType aType) +BroadcastSetThreadSandbox(UniquePtr<sock_filter[]> aProgram, size_t aProgLen) { int signum; pid_t pid, tid, myTid; DIR *taskdp; struct dirent *de; - SandboxFilter filter(&sSetSandboxFilter, aType, - SandboxInfo::Get().Test(SandboxInfo::kVerbose)); + + // Note: this is an unsafe copy of the unique pointer, but it's + // zeroed (and the signal handler that would access it is removed) + // before the end of this function. + gSetSandboxFilter.filter = aProgram.get(); + gSetSandboxFilter.len = static_cast<unsigned short>(aProgLen); + MOZ_RELEASE_ASSERT(static_cast<size_t>(gSetSandboxFilter.len) == aProgLen); static_assert(sizeof(mozilla::Atomic<int>) == sizeof(int), "mozilla::Atomic<int> isn't represented by an int"); pid = getpid(); myTid = syscall(__NR_gettid); taskdp = opendir("/proc/self/task"); if (taskdp == nullptr) { SANDBOX_LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno)); @@ -327,17 +330,17 @@ BroadcastSetThreadSandbox(SandboxType aT continue; } if (tid == myTid) { // Drop this thread's privileges last, below, so we can // continue to signal other threads. continue; } // Reset the futex cell and signal. - sSetSandboxDone = 0; + gSetSandboxDone = 0; if (syscall(__NR_tgkill, pid, tid, signum) != 0) { if (errno == ESRCH) { SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid); // Rescan threads, in case it forked before exiting. sandboxProgress = true; continue; } SANDBOX_LOG_ERROR("tgkill(%d,%d): %s\n", pid, tid, strerror(errno)); @@ -358,27 +361,27 @@ BroadcastSetThreadSandbox(SandboxType aT // blocking forever or silently losing security, and it // shouldn't actually happen. static const int crashDelay = 10; // seconds struct timespec timeLimit; clock_gettime(CLOCK_MONOTONIC, &timeLimit); timeLimit.tv_sec += crashDelay; while (true) { static const struct timespec futexTimeout = { 0, 10*1000*1000 }; // 10ms - // Atomically: if sSetSandboxDone == 0, then sleep. - if (syscall(__NR_futex, reinterpret_cast<int*>(&sSetSandboxDone), + // Atomically: if gSetSandboxDone == 0, then sleep. + if (syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone), FUTEX_WAIT, 0, &futexTimeout) != 0) { if (errno != EWOULDBLOCK && errno != ETIMEDOUT && errno != EINTR) { SANDBOX_LOG_ERROR("FUTEX_WAIT: %s\n", strerror(errno)); MOZ_CRASH(); } } // Did the handler finish? - if (sSetSandboxDone > 0) { - if (sSetSandboxDone == 2) { + if (gSetSandboxDone > 0) { + if (gSetSandboxDone == 2) { sandboxProgress = true; } break; } // Has the thread ceased to exist? if (syscall(__NR_tgkill, pid, tid, 0) != 0) { if (errno == ESRCH) { SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid); @@ -408,37 +411,51 @@ BroadcastSetThreadSandbox(SandboxType aT // See the comment on FindFreeSignalNumber about race conditions. SANDBOX_LOG_ERROR("handler for signal %d was changed to %p!", signum, oldHandler); MOZ_CRASH(); } unused << closedir(taskdp); // And now, deprivilege the main thread: SetThreadSandbox(); + gSetSandboxFilter.filter = nullptr; } // Common code for sandbox startup. static void -SetCurrentProcessSandbox(SandboxType aType) +SetCurrentProcessSandbox(UniquePtr<sandbox::bpf_dsl::Policy> aPolicy) { MOZ_ASSERT(gSandboxCrashFunc); - if (InstallSyscallReporter()) { - SANDBOX_LOG_ERROR("install_syscall_reporter() failed\n"); + // Note: PolicyCompiler borrows the policy and registry for its + // lifetime, but does not take ownership of them. + sandbox::bpf_dsl::PolicyCompiler compiler(aPolicy.get(), + sandbox::Trap::Registry()); + auto program = compiler.Compile(); + if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { + sandbox::bpf_dsl::DumpBPF::PrintProgram(*program); } + InstallSigSysHandler(); + #ifdef MOZ_ASAN __sanitizer_sandbox_arguments asanArgs; asanArgs.coverage_sandboxed = 1; asanArgs.coverage_fd = -1; asanArgs.coverage_max_block_size = 0; __sanitizer_sandbox_on_notify(&asanArgs); #endif - BroadcastSetThreadSandbox(aType); + // The syscall takes a C-style array, so copy the vector into one. + UniquePtr<sock_filter[]> flatProgram(new sock_filter[program->size()]); + for (auto i = program->begin(); i != program->end(); ++i) { + flatProgram[i - program->begin()] = *i; + } + + BroadcastSetThreadSandbox(Move(flatProgram), program->size()); } void SandboxEarlyInit(GeckoProcessType aType, bool aIsNuwa) { MOZ_RELEASE_ASSERT(IsSingleThreaded()); // Which kinds of resource isolation (of those that need to be set @@ -525,46 +542,49 @@ SandboxEarlyInit(GeckoProcessType aType, */ void SetContentProcessSandbox() { if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) { return; } - SetCurrentProcessSandbox(kSandboxContentProcess); + SetCurrentProcessSandbox(GetContentSandboxPolicy()); } #endif // MOZ_CONTENT_SANDBOX #ifdef MOZ_GMP_SANDBOX /** * Starts the seccomp sandbox for a media plugin process. Should be * called only once, and before any potentially harmful content is * loaded -- including the plugin itself, if it's considered untrusted. * - * The file indicated by aFilePath, if non-null, can be open()ed once - * read-only after the sandbox starts; it should be the .so file - * implementing the not-yet-loaded plugin. + * The file indicated by aFilePath, if non-null, can be open()ed + * read-only, once, after the sandbox starts; it should be the .so + * file implementing the not-yet-loaded plugin. * * Will normally make the process exit on failure. */ void SetMediaPluginSandbox(const char *aFilePath) { if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia)) { return; } + MOZ_ASSERT(!gMediaPluginFile.mPath); if (aFilePath) { - gMediaPluginFilePath = strdup(aFilePath); - gMediaPluginFileDesc = open(aFilePath, O_RDONLY | O_CLOEXEC); - if (gMediaPluginFileDesc == -1) { + gMediaPluginFile.mPath = strdup(aFilePath); + gMediaPluginFile.mFd = open(aFilePath, O_RDONLY | O_CLOEXEC); + if (gMediaPluginFile.mFd == -1) { SANDBOX_LOG_ERROR("failed to open plugin file %s: %s", aFilePath, strerror(errno)); MOZ_CRASH(); } + } else { + gMediaPluginFile.mFd = -1; } // Finally, start the sandbox. - SetCurrentProcessSandbox(kSandboxMediaPlugin); + SetCurrentProcessSandbox(GetMediaSandboxPolicy(&gMediaPluginFile)); } #endif // MOZ_GMP_SANDBOX } // namespace mozilla
deleted file mode 100644 --- a/security/sandbox/linux/SandboxAssembler.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "SandboxAssembler.h" - -#include <errno.h> -#include <utility> - -#include "sandbox/linux/bpf_dsl/dump_bpf.h" -#include "sandbox/linux/bpf_dsl/trap_registry.h" -#include "sandbox/linux/seccomp-bpf/codegen.h" -#include "sandbox/linux/seccomp-bpf/linux_seccomp.h" - -namespace mozilla { - -// Need this for the SECCOMP_*_IDX macros to work. -using sandbox::arch_seccomp_data; - -class SandboxAssemblerImpl { - typedef sandbox::Instruction* NodePtr; - - sandbox::CodeGen mCode; - NodePtr mHead; - - NodePtr RetAllow(); - NodePtr RetDeny(int aErrno); - NodePtr RetKill(); - NodePtr LoadArch(NodePtr aNext); - NodePtr LoadSyscall(NodePtr aNext); - NodePtr LoadArgHi(int aArg, NodePtr aNext); - NodePtr LoadArgLo(int aArg, NodePtr aNext); - NodePtr JmpEq(uint32_t aValue, NodePtr aThen, NodePtr aElse); -public: - SandboxAssemblerImpl(); - void PrependCheck(int aMaybeError, int aSyscallNr); - void PrependCheck(int aMaybeError, int aSyscallNr, int aArgChecked, - const std::vector<uint32_t>& aArgValues); - void Compile(std::vector<sock_filter>* aProgram, bool aPrint); -}; - -void -SandboxAssemblerImpl::Compile(std::vector<sock_filter>* aProgram, bool aPrint) -{ - NodePtr prog = LoadSyscall(mHead); - prog = LoadArch(JmpEq(SECCOMP_ARCH, prog, RetKill())); - - mCode.Compile(prog, aProgram); - if (aPrint) { - sandbox::bpf_dsl::DumpBPF::PrintProgram(*aProgram); - } -} - -void -SandboxAssemblerImpl::PrependCheck(int aMaybeError, int aSyscallNr) -{ - NodePtr ret = aMaybeError ? RetDeny(aMaybeError) : RetAllow(); - mHead = JmpEq(aSyscallNr, ret, mHead); -} - -void -SandboxAssemblerImpl::PrependCheck(int aMaybeError, int aSyscallNr, - int aArgChecked, - const std::vector<uint32_t>& aArgValues) -{ - NodePtr ret = aMaybeError ? RetDeny(aMaybeError) : RetAllow(); - NodePtr noMatch = mHead; - NodePtr checkArg = LoadSyscall(noMatch); - - // Loop backwards, prepending checks onto the no-match base case. - for (auto i = aArgValues.rbegin(); i != aArgValues.rend(); ++i) { - checkArg = JmpEq(*i, ret, checkArg); - } - checkArg = LoadArgLo(aArgChecked, checkArg); - checkArg = LoadArgHi(aArgChecked, JmpEq(0, checkArg, RetKill())); - - mHead = JmpEq(aSyscallNr, checkArg, noMatch); -} - -SandboxAssemblerImpl::SandboxAssemblerImpl() { - mHead = RetKill(); -} - -void -SandboxAssembler::Compile(std::vector<sock_filter>* aProgram, bool aPrint) -{ - SandboxAssemblerImpl impl; - - for (auto i = mRuleStack.rbegin(); i != mRuleStack.rend(); ++i) { - if (i->second.mCheckingArg) { - impl.PrependCheck(i->first, i->second.mSyscallNr, i->second.mArgChecked, - i->second.mArgValues); - } else { - impl.PrependCheck(i->first, i->second.mSyscallNr); - } - } - - impl.Compile(aProgram, aPrint); -} - - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::LoadArch(NodePtr aNext) -{ - return mCode.MakeInstruction(BPF_LD + BPF_W + BPF_ABS, - SECCOMP_ARCH_IDX, - aNext); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::LoadSyscall(NodePtr aNext) -{ - return mCode.MakeInstruction(BPF_LD + BPF_W + BPF_ABS, - SECCOMP_NR_IDX, - aNext); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::LoadArgHi(int aArg, NodePtr aNext) -{ - return mCode.MakeInstruction(BPF_LD + BPF_W + BPF_ABS, - SECCOMP_ARG_MSB_IDX(aArg), - aNext); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::LoadArgLo(int aArg, NodePtr aNext) -{ - return mCode.MakeInstruction(BPF_LD + BPF_W + BPF_ABS, - SECCOMP_ARG_LSB_IDX(aArg), - aNext); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::JmpEq(uint32_t aValue, NodePtr aThen, NodePtr aElse) -{ - return mCode.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, - aValue, aThen, aElse); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::RetAllow() -{ - return mCode.MakeInstruction(BPF_RET + BPF_K, - SECCOMP_RET_ALLOW, - nullptr); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::RetDeny(int aErrno) -{ - return mCode.MakeInstruction(BPF_RET + BPF_K, - SECCOMP_RET_ERRNO + aErrno, - nullptr); -} - -SandboxAssemblerImpl::NodePtr -SandboxAssemblerImpl::RetKill() -{ - return mCode.MakeInstruction(BPF_RET + BPF_K, - SECCOMP_RET_TRAP, - nullptr); -} - -} // namespace mozilla
deleted file mode 100644 --- a/security/sandbox/linux/SandboxAssembler.h +++ /dev/null @@ -1,77 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* 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/. */ - -#ifndef mozilla_SandboxAssembler_h -#define mozilla_SandboxAssembler_h - -#include <vector> - -#include "mozilla/Assertions.h" - -struct sock_filter; - -namespace mozilla { - -class SandboxAssembler { -public: - class Condition { - friend class SandboxAssembler; - uint32_t mSyscallNr; - bool mCheckingArg; - uint8_t mArgChecked; - // This class retains a copy of the array, because C++11 - // initializer_list isn't supported on all relevant platforms. - std::vector<uint32_t> mArgValues; - public: - // Match any instance of the given syscall, with any arguments. - explicit Condition(uint32_t aSyscallNr) - : mSyscallNr(aSyscallNr) - , mCheckingArg(false) - { } - // Match the syscall only if the given argument is one of the - // values in the array specified. (If the argument isn't - // representable as uint32, the process is killed or signaled, as - // appropriate.) - template<size_t n> - Condition(uint32_t aSyscallNr, uint8_t aArgChecked, - const uint32_t (&aArgValues)[n]) - : mSyscallNr(aSyscallNr) - , mCheckingArg(true) - , mArgChecked(aArgChecked) - , mArgValues(aArgValues, aArgValues + n) - { - MOZ_ASSERT(aArgChecked < sNumArgs); - } - // This isn't perf-critical, so a naive copy ctor is fine. - Condition(const Condition& aOther) - : mSyscallNr(aOther.mSyscallNr) - , mCheckingArg(aOther.mCheckingArg) - , mArgChecked(aOther.mArgChecked) - , mArgValues(aOther.mArgValues) - { } - }; - - // Allow syscalls matching this condition, if no earlier condition matched. - void Allow(const Condition& aCond) { - mRuleStack.push_back(std::make_pair(0, aCond)); - } - // Cause syscalls matching this condition to fail with the given error, if - // no earlier condition matched. - void Deny(int aErrno, const Condition& aCond) { - MOZ_ASSERT(aErrno != 0); - mRuleStack.push_back(std::make_pair(aErrno, aCond)); - } - - void Compile(std::vector<sock_filter>* aProgram, bool aPrint = false); -private: - std::vector<std::pair<int, Condition>> mRuleStack; - - static const uint8_t sNumArgs = 6; -}; - -} - -#endif
--- a/security/sandbox/linux/SandboxFilter.cpp +++ b/security/sandbox/linux/SandboxFilter.cpp @@ -1,502 +1,599 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SandboxFilter.h" -#include "SandboxAssembler.h" +#include "SandboxFilterUtil.h" +#include "SandboxInternal.h" +#include "SandboxLogging.h" + +#include "mozilla/UniquePtr.h" #include <errno.h> +#include <fcntl.h> #include <linux/ipc.h> #include <linux/net.h> #include <linux/prctl.h> #include <linux/sched.h> #include <sys/mman.h> +#include <sys/socket.h> +#include <sys/syscall.h> #include <time.h> #include <unistd.h> -#include "mozilla/ArrayUtils.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" #include "sandbox/linux/services/linux_syscalls.h" +using namespace sandbox::bpf_dsl; +#define CASES SANDBOX_BPF_DSL_CASES + +// To avoid visual confusion between "ifdef ANDROID" and "ifndef ANDROID": +#ifndef ANDROID +#define DESKTOP +#endif + +// This file defines the seccomp-bpf system call filter policies. +// See also SandboxFilterUtil.h, for the CASES_FOR_* macros and +// SandboxFilterBase::Evaluate{Socket,Ipc}Call. +// +// One important difference from how Chromium bpf_dsl filters are +// normally interpreted: returning -ENOSYS from a Trap() handler +// indicates an unexpected system call; SigSysHandler() in Sandbox.cpp +// will detect this, request a crash dump, and terminate the process. +// This does not apply to using Error(ENOSYS) in the policy, so that +// can be used if returning an actual ENOSYS is needed. + namespace mozilla { -class SandboxFilterImpl : public SandboxAssembler +// This class whitelists everything used by the sandbox itself, by the +// core IPC code, by the crash reporter, or other core code. +class SandboxPolicyCommon : public SandboxPolicyBase { -public: - virtual void Build() = 0; - virtual ~SandboxFilterImpl() { } - void AllowThreadClone(); -}; - -// Some helper macros to make the code that builds the filter more -// readable, and to help deal with differences among architectures. - -#define SYSCALL_EXISTS(name) (defined(__NR_##name)) - -#define SYSCALL(name) (Condition(__NR_##name)) -#if defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) -#define ARM_SYSCALL(name) (Condition(__ARM_NR_##name)) -#endif - -#define SYSCALL_WITH_ARG(name, arg, values...) ({ \ - uint32_t argValues[] = { values }; \ - Condition(__NR_##name, arg, argValues); \ -}) + static intptr_t BlockedSyscallTrap(const sandbox::arch_seccomp_data& aArgs, + void *aux) + { + MOZ_ASSERT(!aux); + return -ENOSYS; + } -// Some architectures went through a transition from 32-bit to -// 64-bit off_t and had to version all the syscalls that referenced -// it; others (newer and/or 64-bit ones) didn't. Adjust the -// conditional as needed. -#if SYSCALL_EXISTS(stat64) -#define SYSCALL_LARGEFILE(plain, versioned) SYSCALL(versioned) -#else -#define SYSCALL_LARGEFILE(plain, versioned) SYSCALL(plain) -#endif - -// i386 multiplexes all the socket-related interfaces into a single -// syscall. -#if SYSCALL_EXISTS(socketcall) -#define SOCKETCALL(name, NAME) SYSCALL_WITH_ARG(socketcall, 0, SYS_##NAME) -#else -#define SOCKETCALL(name, NAME) SYSCALL(name) -#endif - -// i386 multiplexes all the SysV-IPC-related interfaces into a single -// syscall. -#if SYSCALL_EXISTS(ipc) -#define SYSVIPCCALL(name, NAME) SYSCALL_WITH_ARG(ipc, 0, NAME) -#else -#define SYSVIPCCALL(name, NAME) SYSCALL(name) +#if defined(ANDROID) && ANDROID_VERSION < 16 + // Bug 1093893: Translate tkill to tgkill for pthread_kill; fixed in + // bionic commit 10c8ce59a (in JB and up; API level 16 = Android 4.1). + static intptr_t TKillCompatTrap(const sandbox::arch_seccomp_data& aArgs, + void *aux) + { + return syscall(__NR_tgkill, getpid(), aArgs.args[0], aArgs.args[1]); + } #endif -void SandboxFilterImpl::AllowThreadClone() { - // WARNING: s390 and cris pass the flags in a different arg -- see - // CLONE_BACKWARDS2 in arch/Kconfig in the kernel source -- but we - // don't support seccomp-bpf on those archs yet. - // - // The glibc source hasn't changed the thread creation clone flags - // since 2004, so this *should* be safe to hard-code. Bionic's - // value has changed a few times, and has converged on the same one - // as glibc; allow any of them. - static const int flags_common = CLONE_VM | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM; - Allow(SYSCALL_WITH_ARG(clone, 0, +public: + virtual ResultExpr InvalidSyscall() const override { + return Trap(BlockedSyscallTrap, nullptr); + } + + virtual ResultExpr ClonePolicy() const { + // Allow use for simple thread creation (pthread_create) only. + + // WARNING: s390 and cris pass the flags in the second arg -- see + // CLONE_BACKWARDS2 in arch/Kconfig in the kernel source -- but we + // don't support seccomp-bpf on those archs yet. + Arg<int> flags(0); + + // The glibc source hasn't changed the thread creation clone flags + // since 2004, so this *should* be safe to hard-code. Bionic's + // value has changed a few times, and has converged on the same one + // as glibc; allow any of them. + static const int flags_common = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM; + static const int flags_modern = flags_common | CLONE_SETTLS | + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + + // Can't use CASES here because its decltype magic infers const + // int instead of regular int and bizarre voluminous errors issue + // forth from the depths of the standard library implementation. + return Switch(flags) #ifdef ANDROID - flags_common | CLONE_DETACHED, // <= JB 4.2 - flags_common, // JB 4.3 or KK 4.4 + .Case(flags_common | CLONE_DETACHED, Allow()) // <= JB 4.2 + .Case(flags_common, Allow()) // JB 4.3 or KK 4.4 #endif - flags_common | CLONE_SETTLS // Android L or glibc - | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID)); -} + .Case(flags_modern, Allow()) // Android L or glibc + .Default(InvalidSyscall()); + } -#ifdef MOZ_CONTENT_SANDBOX -class SandboxFilterImplContent : public SandboxFilterImpl { -protected: - virtual void Build() override; -}; + virtual ResultExpr PrctlPolicy() const { + // Note: this will probably need PR_SET_VMA if/when it's used on + // Android without being overridden by an allow-all policy, and + // the constant will need to be defined locally. + Arg<int> op(0); + return Switch(op) + .CASES((PR_GET_SECCOMP, // BroadcastSetThreadSandbox, etc. + PR_SET_NAME, // Thread creation + PR_SET_DUMPABLE), // Crash reporting + Allow()) + .Default(InvalidSyscall()); + } + + virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const override { + switch (aCall) { + case SYS_RECVMSG: + case SYS_SENDMSG: + return Some(Allow()); + default: + return Nothing(); + } + } -void -SandboxFilterImplContent::Build() { - /* Most used system calls should be at the top of the whitelist - * for performance reasons. The whitelist BPF filter exits after - * processing any ALLOW_SYSCALL macro. - * - * How are those syscalls found? - * 1) via strace -p <child pid> or/and - * 2) the child will report which system call has been denied by seccomp-bpf, - * just before exiting - * System call number to name mapping is found in: - * bionic/libc/kernel/arch-arm/asm/unistd.h - * or your libc's unistd.h/kernel headers. - * - * Current list order has been optimized through manual guess-work. - * It could be further optimized by analyzing the output of: - * 'strace -c -p <child pid>' for most used web apps. - */ + virtual ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + // Timekeeping + case __NR_clock_gettime: { + Arg<clockid_t> clk_id(0); + return If(clk_id == CLOCK_MONOTONIC, Allow()) + .ElseIf(clk_id == CLOCK_REALTIME, Allow()) + .Else(InvalidSyscall()); + } + case __NR_gettimeofday: +#ifdef __NR_time + case __NR_time: +#endif + case __NR_nanosleep: + return Allow(); + + // Thread synchronization + case __NR_futex: + // FIXME: This could be more restrictive.... + return Allow(); + + // Asynchronous I/O + case __NR_epoll_wait: + case __NR_epoll_pwait: + case __NR_epoll_ctl: + case __NR_ppoll: + case __NR_poll: + return Allow(); + + // Used when requesting a crash dump. + case __NR_pipe: + return Allow(); - Allow(SYSCALL(futex)); - Allow(SOCKETCALL(recvmsg, RECVMSG)); - Allow(SOCKETCALL(sendmsg, SENDMSG)); + // Metadata of opened files + CASES_FOR_fstat: + return Allow(); + + // Simple I/O + case __NR_write: + case __NR_read: + return Allow(); + + // Memory mapping + CASES_FOR_mmap: + case __NR_munmap: + return Allow(); - // mmap2 is a little different from most off_t users, because it's - // passed in a register (so it's a problem for even a "new" 32-bit - // arch) -- and the workaround, mmap2, passes a page offset instead. -#if SYSCALL_EXISTS(mmap2) - Allow(SYSCALL(mmap2)); -#else - Allow(SYSCALL(mmap)); + // Signal handling +#if defined(ANDROID) || defined(MOZ_ASAN) + case __NR_sigaltstack: +#endif + CASES_FOR_sigreturn: + CASES_FOR_sigprocmask: + CASES_FOR_sigaction: + return Allow(); + + // Send signals within the process (raise(), profiling, etc.) + case __NR_tgkill: { + Arg<pid_t> tgid(0); + return If(tgid == getpid(), Allow()) + .Else(InvalidSyscall()); + } + +#if defined(ANDROID) && ANDROID_VERSION < 16 + // Polyfill with tgkill; see above. + case __NR_tkill: + return Trap(TKillCompatTrap, nullptr); #endif - Allow(SYSCALL(clock_gettime)); - Allow(SYSCALL(epoll_wait)); - Allow(SYSCALL(epoll_pwait)); - Allow(SYSCALL(gettimeofday)); - Allow(SYSCALL(read)); - Allow(SYSCALL(write)); - // 32-bit lseek is used, at least on Android, to implement ANSI fseek. -#if SYSCALL_EXISTS(_llseek) - Allow(SYSCALL(_llseek)); + // Yield + case __NR_sched_yield: + return Allow(); + + // Thread creation. + case __NR_clone: + return ClonePolicy(); + + // More thread creation. +#ifdef __NR_set_robust_list + case __NR_set_robust_list: + return Allow(); #endif - Allow(SYSCALL(lseek)); - // Android also uses 32-bit ftruncate. - Allow(SYSCALL(ftruncate)); -#if SYSCALL_EXISTS(ftruncate64) - Allow(SYSCALL(ftruncate64)); +#ifdef ANDROID + case __NR_set_tid_address: + return Allow(); #endif - /* ioctl() is for GL. Remove when GL proxy is implemented. - * Additionally ioctl() might be a place where we want to have - * argument filtering */ - Allow(SYSCALL(ioctl)); - Allow(SYSCALL(close)); - Allow(SYSCALL(munmap)); - Allow(SYSCALL(mprotect)); - Allow(SYSCALL(writev)); - Allow(SYSCALL(pread64)); - AllowThreadClone(); - Allow(SYSCALL(brk)); -#if SYSCALL_EXISTS(set_thread_area) - Allow(SYSCALL(set_thread_area)); + // prctl + case __NR_prctl: + return PrctlPolicy(); + + // NSPR can call this when creating a thread, but it will accept a + // polite "no". + case __NR_getpriority: + // But if thread creation races with sandbox startup, that call + // could succeed, and then we get one of these: + case __NR_setpriority: + return Error(EACCES); + + // Stack bounds are obtained via pthread_getattr_np, which calls + // this but doesn't actually need it: + case __NR_sched_getaffinity: + return Error(ENOSYS); + + // Read own pid/tid. + case __NR_getpid: + case __NR_gettid: + return Allow(); + + // Discard capabilities + case __NR_close: + return Allow(); + + // Machine-dependent stuff +#ifdef __arm__ + case __ARM_NR_breakpoint: + case __ARM_NR_cacheflush: + case __ARM_NR_usr26: // FIXME: do we actually need this? + case __ARM_NR_usr32: + case __ARM_NR_set_tls: + return Allow(); #endif - Allow(SYSCALL(getpid)); - Allow(SYSCALL(gettid)); - Allow(SYSCALL(getrusage)); - Allow(SYSCALL(times)); - Allow(SYSCALL(madvise)); - Allow(SYSCALL(dup)); - Allow(SYSCALL(nanosleep)); - Allow(SYSCALL(poll)); - Allow(SYSCALL(ppoll)); - Allow(SYSCALL(openat)); - Allow(SYSCALL(faccessat)); - // select()'s arguments used to be passed by pointer as a struct. -#if SYSCALL_EXISTS(_newselect) - Allow(SYSCALL(_newselect)); -#else - Allow(SYSCALL(select)); + // Needed when being debugged: + case __NR_restart_syscall: + return Allow(); + + // Terminate threads or the process + case __NR_exit: + case __NR_exit_group: + return Allow(); + +#ifdef MOZ_ASAN + // ASAN's error reporter wants to know if stderr is a tty. + case __NR_ioctl: { + Arg<int> fd(0); + return If(fd == STDERR_FILENO, Allow()) + .Else(InvalidSyscall()); + } + + // ...and before compiler-rt r209773, it will call readlink on + // /proc/self/exe and use the cached value only if that fails: + case __NR_readlink: + case __NR_readlinkat: + return Error(ENOENT); + + // ...and if it found an external symbolizer, it will try to run it: + // (See also bug 1081242 comment #7.) + CASES_FOR_stat: + return Error(ENOENT); #endif - Allow(SYSCALL(pselect6)); - // Some archs used to have 16-bit uid/gid instead of 32-bit. -#if SYSCALL_EXISTS(getuid32) - Allow(SYSCALL(getuid32)); - Allow(SYSCALL(geteuid32)); - Allow(SYSCALL(getgid32)); - Allow(SYSCALL(getegid32)); -#else - Allow(SYSCALL(getuid)); - Allow(SYSCALL(geteuid)); - Allow(SYSCALL(getgid)); - Allow(SYSCALL(getegid)); -#endif - // Some newer archs (e.g., x64 and x32) have only rt_sigreturn, but - // ARM has and uses both syscalls -- rt_sigreturn for SA_SIGINFO - // handlers and classic sigreturn otherwise. -#if SYSCALL_EXISTS(sigreturn) - Allow(SYSCALL(sigreturn)); + + default: + return SandboxPolicyBase::EvaluateSyscall(sysno); + } + } +}; + +// The process-type-specific syscall rules start here: + +#ifdef MOZ_CONTENT_SANDBOX +// The seccomp-bpf filter for content processes is not a true sandbox +// on its own; its purpose is attack surface reduction and syscall +// interception in support of a semantic sandboxing layer. On B2G +// this is the Android process permission model; on desktop, +// namespaces and chroot() will be used. +class ContentSandboxPolicy : public SandboxPolicyCommon { +public: + ContentSandboxPolicy() { } + virtual ~ContentSandboxPolicy() { } + virtual ResultExpr PrctlPolicy() const override { + // Ideally this should be restricted to a whitelist, but content + // uses enough things that it's not trivial to determine it. + return Allow(); + } + virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const override { + switch(aCall) { + case SYS_RECVFROM: + case SYS_SENDTO: + return Some(Allow()); + + case SYS_SOCKETPAIR: { + // See bug 1066750. + if (!kSocketCallHasArgs) { + // We can't filter the args if the platform passes them by pointer. + return Some(Allow()); + } + Arg<int> domain(0), type(1); + return Some(If(domain == AF_UNIX && + (type == SOCK_STREAM || type == SOCK_SEQPACKET), Allow()) + .Else(InvalidSyscall())); + } + +#ifdef ANDROID + case SYS_SOCKET: + return Some(Error(EACCES)); +#else // #ifdef DESKTOP + case SYS_RECV: + case SYS_SEND: + case SYS_SOCKET: // DANGEROUS + case SYS_CONNECT: // DANGEROUS + case SYS_SETSOCKOPT: + case SYS_GETSOCKNAME: + case SYS_GETPEERNAME: + case SYS_SHUTDOWN: + return Some(Allow()); #endif - Allow(SYSCALL(rt_sigreturn)); - Allow(SYSCALL_LARGEFILE(fcntl, fcntl64)); + default: + return SandboxPolicyCommon::EvaluateSocketCall(aCall); + } + } - /* Must remove all of the following in the future, when no longer used */ - /* open() is for some legacy APIs such as font loading. */ - Allow(SYSCALL_LARGEFILE(fstat, fstat64)); - Allow(SYSCALL_LARGEFILE(stat, stat64)); - Allow(SYSCALL_LARGEFILE(lstat, lstat64)); - Allow(SYSCALL_LARGEFILE(newfstatat, fstatat64)); - Allow(SOCKETCALL(socketpair, SOCKETPAIR)); - Deny(EACCES, SOCKETCALL(socket, SOCKET)); - Allow(SYSCALL(open)); - Deny(EINVAL, SYSCALL(readlink)); /* Workaround for bug 964455 */ - Deny(EINVAL, SYSCALL(readlinkat)); /* Workaround for bug 964455 */ - Allow(SYSCALL(prctl)); - Allow(SYSCALL(access)); - Allow(SYSCALL(fsync)); - Allow(SYSCALL(msync)); - -#if defined(ANDROID) && !defined(MOZ_MEMORY) - // Android's libc's realloc uses mremap. - Allow(SYSCALL(mremap)); +#ifdef DESKTOP + virtual Maybe<ResultExpr> EvaluateIpcCall(int aCall) const override { + switch(aCall) { + // These are a problem: SysV shared memory follows the Unix + // "same uid policy" and can't be restricted/brokered like file + // access. But the graphics layer might not be using them + // anymore; this needs to be studied. + case SHMGET: + case SHMCTL: + case SHMAT: + case SHMDT: + return Some(Allow()); + default: + return SandboxPolicyCommon::EvaluateIpcCall(aCall); + } + } #endif - /* Should remove all of the following in the future, if possible */ - Allow(SYSCALL(getpriority)); - Allow(SYSCALL(sched_get_priority_min)); - Allow(SYSCALL(sched_get_priority_max)); - Allow(SYSCALL(setpriority)); - // rt_sigprocmask is passed the sigset_t size. On older archs, - // sigprocmask is a compatibility shim that assumes the pre-RT size. -#if SYSCALL_EXISTS(sigprocmask) - Allow(SYSCALL(sigprocmask)); + virtual ResultExpr EvaluateSyscall(int sysno) const override { + switch(sysno) { + // Filesystem operations we need to get rid of; see bug 930258. + case __NR_open: + case __NR_openat: + case __NR_access: + case __NR_faccessat: + CASES_FOR_stat: + CASES_FOR_lstat: + CASES_FOR_fstatat: +#ifdef DESKTOP + // Some if not all of these can probably be soft-failed or + // removed entirely; this needs to be studied. + case __NR_mkdir: + case __NR_rmdir: + case __NR_getcwd: + CASES_FOR_statfs: + case __NR_chmod: + case __NR_rename: + case __NR_symlink: + case __NR_quotactl: + case __NR_utimes: #endif - Allow(SYSCALL(rt_sigprocmask)); + return Allow(); - // Used by profiler. Also used for raise(), which causes problems - // with Android KitKat abort(); see bug 1004832. - Allow(SYSCALL_WITH_ARG(tgkill, 0, uint32_t(getpid()))); + case __NR_readlink: + case __NR_readlinkat: + // Workaround for bug 964455: + return Error(EINVAL); + + CASES_FOR_select: + case __NR_pselect6: + return Allow(); - Allow(SOCKETCALL(sendto, SENDTO)); - Allow(SOCKETCALL(recvfrom, RECVFROM)); - Allow(SYSCALL_LARGEFILE(getdents, getdents64)); - Allow(SYSCALL(epoll_ctl)); - Allow(SYSCALL(sched_yield)); - Allow(SYSCALL(sched_getscheduler)); - Allow(SYSCALL(sched_setscheduler)); - Allow(SYSCALL(sched_getparam)); - Allow(SYSCALL(sched_setparam)); - Allow(SYSCALL(sigaltstack)); - Allow(SYSCALL(pipe)); - Allow(SYSCALL(set_tid_address)); + CASES_FOR_getdents: + CASES_FOR_lseek: + CASES_FOR_ftruncate: + case __NR_writev: + case __NR_pread64: +#ifdef DESKTOP + case __NR_readahead: +#endif + return Allow(); + + case __NR_ioctl: + // ioctl() is for GL. Remove when GL proxy is implemented. + // Additionally ioctl() might be a place where we want to have + // argument filtering + return Allow(); - /* Always last and always OK calls */ - /* Architecture-specific very infrequently used syscalls */ -#if SYSCALL_EXISTS(sigaction) - Allow(SYSCALL(sigaction)); + CASES_FOR_fcntl: + // Some fcntls have significant side effects like sending + // arbitrary signals, and there's probably nontrivial kernel + // attack surface; this should be locked down more if possible. + return Allow(); + + case __NR_mprotect: + case __NR_brk: + case __NR_madvise: +#if defined(ANDROID) && !defined(MOZ_MEMORY) + // Android's libc's realloc uses mremap. + case __NR_mremap: #endif - Allow(SYSCALL(rt_sigaction)); -#ifdef ARM_SYSCALL - Allow(ARM_SYSCALL(breakpoint)); - Allow(ARM_SYSCALL(cacheflush)); - Allow(ARM_SYSCALL(usr26)); - Allow(ARM_SYSCALL(usr32)); - Allow(ARM_SYSCALL(set_tls)); + return Allow(); + + case __NR_sigaltstack: + return Allow(); + +#ifdef __NR_set_thread_area + case __NR_set_thread_area: + return Allow(); #endif - /* restart_syscall is called internally, generally when debugging */ - Allow(SYSCALL(restart_syscall)); + case __NR_getrusage: + case __NR_times: + return Allow(); + + case __NR_dup: + return Allow(); + + CASES_FOR_getuid: + CASES_FOR_getgid: + CASES_FOR_geteuid: + CASES_FOR_getegid: + return Allow(); - /* linux desktop is not as performance critical as mobile */ - /* we can place desktop syscalls at the end */ -#ifndef ANDROID - Allow(SYSCALL(stat)); - Allow(SYSCALL(getdents)); - Allow(SYSCALL(lstat)); -#if SYSCALL_EXISTS(mmap2) - Allow(SYSCALL(mmap2)); -#else - Allow(SYSCALL(mmap)); -#endif - Allow(SYSCALL(openat)); - Allow(SYSCALL(fcntl)); - Allow(SYSCALL(fstat)); - Allow(SYSCALL(readlink)); - Allow(SOCKETCALL(getsockname, GETSOCKNAME)); - Allow(SYSCALL(getuid)); - Allow(SYSCALL(geteuid)); - Allow(SYSCALL(mkdir)); - Allow(SYSCALL(getcwd)); - Allow(SYSCALL(readahead)); - Allow(SYSCALL(pread64)); - Allow(SYSCALL(statfs)); -#if SYSCALL_EXISTS(ugetrlimit) - Allow(SYSCALL(ugetrlimit)); -#else - Allow(SYSCALL(getrlimit)); + case __NR_fsync: + case __NR_msync: + return Allow(); + + case __NR_getpriority: + case __NR_setpriority: + case __NR_sched_get_priority_min: + case __NR_sched_get_priority_max: + case __NR_sched_getscheduler: + case __NR_sched_setscheduler: + case __NR_sched_getparam: + case __NR_sched_setparam: +#ifdef DESKTOP + case __NR_sched_getaffinity: #endif - Allow(SOCKETCALL(shutdown, SHUTDOWN)); - Allow(SOCKETCALL(getpeername, GETPEERNAME)); - Allow(SYSCALL(eventfd2)); - Allow(SYSCALL(clock_getres)); - Allow(SYSCALL(sysinfo)); - Allow(SYSCALL(getresuid)); - Allow(SYSCALL(umask)); - Allow(SYSCALL(getresgid)); - Allow(SYSCALL(poll)); - Allow(SYSCALL(ppoll)); - Allow(SYSCALL(openat)); - Allow(SYSCALL(faccessat)); - Allow(SYSCALL(inotify_init1)); - Allow(SYSCALL(wait4)); - Allow(SYSVIPCCALL(shmctl, SHMCTL)); - Allow(SYSCALL(set_robust_list)); - Allow(SYSCALL(rmdir)); - Allow(SOCKETCALL(recvfrom, RECVFROM)); - Allow(SYSVIPCCALL(shmdt, SHMDT)); - Allow(SYSCALL(pipe2)); - Allow(SOCKETCALL(setsockopt, SETSOCKOPT)); - Allow(SYSVIPCCALL(shmat, SHMAT)); - Allow(SYSCALL(set_tid_address)); - Allow(SYSCALL(inotify_add_watch)); - Allow(SYSCALL(rt_sigprocmask)); - Allow(SYSVIPCCALL(shmget, SHMGET)); -#if SYSCALL_EXISTS(utimes) - Allow(SYSCALL(utimes)); -#else - Allow(SYSCALL(utime)); + return Allow(); + +#ifdef DESKTOP + case __NR_pipe2: + return Allow(); + + CASES_FOR_getrlimit: + case __NR_clock_getres: + case __NR_getresuid: + case __NR_getresgid: + return Allow(); + + case __NR_umask: + case __NR_kill: + case __NR_wait4: +#ifdef __NR_arch_prctl + case __NR_arch_prctl: #endif -#if SYSCALL_EXISTS(arch_prctl) - Allow(SYSCALL(arch_prctl)); -#endif - Allow(SYSCALL(sched_getaffinity)); - /* We should remove all of the following in the future (possibly even more) */ - Allow(SOCKETCALL(socket, SOCKET)); - Allow(SYSCALL(chmod)); - Allow(SYSCALL(execve)); - Allow(SYSCALL(rename)); - Allow(SYSCALL(symlink)); - Allow(SOCKETCALL(connect, CONNECT)); - Allow(SYSCALL(quotactl)); - Allow(SYSCALL(kill)); - Allow(SOCKETCALL(sendto, SENDTO)); + return Allow(); + + case __NR_eventfd2: + case __NR_inotify_init1: + case __NR_inotify_add_watch: + return Allow(); #endif - /* nsSystemInfo uses uname (and we cache an instance, so */ - /* the info remains present even if we block the syscall) */ - Allow(SYSCALL(uname)); - Allow(SYSCALL(exit_group)); - Allow(SYSCALL(exit)); + // nsSystemInfo uses uname (and we cache an instance, so + // the info remains present even if we block the syscall) + case __NR_uname: +#ifdef DESKTOP + case __NR_sysinfo: +#endif + return Allow(); + + default: + return SandboxPolicyCommon::EvaluateSyscall(sysno); + } + } +}; + +UniquePtr<sandbox::bpf_dsl::Policy> +GetContentSandboxPolicy() +{ + return UniquePtr<sandbox::bpf_dsl::Policy>(new ContentSandboxPolicy()); } #endif // MOZ_CONTENT_SANDBOX + #ifdef MOZ_GMP_SANDBOX -class SandboxFilterImplGMP : public SandboxFilterImpl { -protected: - virtual void Build() override; +// Unlike for content, the GeckoMediaPlugin seccomp-bpf policy needs +// to be an effective sandbox by itself, because we allow GMP on Linux +// systems where that's the only sandboxing mechanism we can use. +// +// Be especially careful about what this policy allows. +class GMPSandboxPolicy : public SandboxPolicyCommon { + static intptr_t OpenTrap(const sandbox::arch_seccomp_data& aArgs, + void* aux) + { + auto plugin = static_cast<SandboxOpenedFile*>(aux); + const char* path; + int flags; + + switch (aArgs.nr) { +#ifdef __NR_open + case __NR_open: + path = reinterpret_cast<const char*>(aArgs.args[0]); + flags = static_cast<int>(aArgs.args[1]); + break; +#endif + case __NR_openat: + // The path has to be absolute to match the pre-opened file (see + // assertion in ctor) so the dirfd argument is ignored. + path = reinterpret_cast<const char*>(aArgs.args[1]); + flags = static_cast<int>(aArgs.args[2]); + break; + default: + MOZ_CRASH("unexpected syscall number"); + } + + if ((flags & O_ACCMODE) != O_RDONLY) { + SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)", + path, flags); + return -ENOSYS; + } + if (strcmp(path, plugin->mPath) != 0) { + SANDBOX_LOG_ERROR("attempt to open file %s which is not the media plugin" + " %s", path, plugin->mPath); + return -ENOSYS; + } + int fd = plugin->mFd.exchange(-1); + if (fd < 0) { + SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented"); + return -ENOSYS; + } + return fd; + } + + SandboxOpenedFile* mPlugin; +public: + explicit GMPSandboxPolicy(SandboxOpenedFile* aPlugin) + : mPlugin(aPlugin) + { + MOZ_ASSERT(aPlugin->mPath[0] == '/', "plugin path should be absolute"); + } + + virtual ~GMPSandboxPolicy() { } + + virtual ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + // Simulate opening the plugin file. +#ifdef __NR_open + case __NR_open: +#endif + case __NR_openat: + return Trap(OpenTrap, mPlugin); + + // ipc::Shmem + case __NR_mprotect: + return Allow(); + case __NR_madvise: { + Arg<int> advice(2); + return If(advice == MADV_DONTNEED, Allow()) + .Else(InvalidSyscall()); + } + + default: + return SandboxPolicyCommon::EvaluateSyscall(sysno); + } + } }; -void SandboxFilterImplGMP::Build() { - // As for content processes, check the most common syscalls first. - - Allow(SYSCALL_WITH_ARG(clock_gettime, 0, CLOCK_MONOTONIC, CLOCK_REALTIME)); - Allow(SYSCALL(futex)); - Allow(SYSCALL(gettimeofday)); - Allow(SYSCALL(poll)); - Allow(SYSCALL(write)); - Allow(SYSCALL(read)); - Allow(SYSCALL(epoll_wait)); - Allow(SYSCALL(epoll_pwait)); - Allow(SOCKETCALL(recvmsg, RECVMSG)); - Allow(SOCKETCALL(sendmsg, SENDMSG)); - Allow(SYSCALL(time)); - Allow(SYSCALL(sched_yield)); - - // Nothing after this line is performance-critical. - -#if SYSCALL_EXISTS(mmap2) - Allow(SYSCALL(mmap2)); -#else - Allow(SYSCALL(mmap)); -#endif - Allow(SYSCALL_LARGEFILE(fstat, fstat64)); - Allow(SYSCALL(munmap)); - - Allow(SYSCALL(getpid)); - Allow(SYSCALL(gettid)); - - AllowThreadClone(); - - Allow(SYSCALL_WITH_ARG(prctl, 0, PR_GET_SECCOMP, PR_SET_NAME)); - -#if SYSCALL_EXISTS(set_robust_list) - Allow(SYSCALL(set_robust_list)); -#endif - - // NSPR can call this when creating a thread, but it will accept a - // polite "no". - Deny(EACCES, SYSCALL(getpriority)); - // But if thread creation races with sandbox startup, that call - // could succeed, and then we get one of these: - Deny(EACCES, SYSCALL(setpriority)); +UniquePtr<sandbox::bpf_dsl::Policy> +GetMediaSandboxPolicy(SandboxOpenedFile* aPlugin) +{ + return UniquePtr<sandbox::bpf_dsl::Policy>(new GMPSandboxPolicy(aPlugin)); +} - // Stack bounds are obtained via pthread_getattr_np, which calls - // this but doesn't actually need it: - Deny(ENOSYS, SYSCALL(sched_getaffinity)); - -#ifdef MOZ_ASAN - Allow(SYSCALL(sigaltstack)); - // ASAN's error reporter wants to know if stderr is a tty. - Deny(ENOTTY, SYSCALL_WITH_ARG(ioctl, 0, STDERR_FILENO)); - // ...and before compiler-rt r209773, it will call readlink and use - // the cached value only if that fails: - Deny(ENOENT, SYSCALL(readlink)); - // ...and if it found an external symbolizer, it will try to run it: - // (See also bug 1081242 comment #7.) - Deny(ENOENT, SYSCALL_LARGEFILE(stat, stat64)); -#endif - - Allow(SYSCALL(mprotect)); - Allow(SYSCALL_WITH_ARG(madvise, 2, MADV_DONTNEED)); - -#if SYSCALL_EXISTS(sigreturn) - Allow(SYSCALL(sigreturn)); -#endif - Allow(SYSCALL(rt_sigreturn)); - - Allow(SYSCALL(restart_syscall)); - Allow(SYSCALL(close)); - - // "Sleeping for 300 seconds" in debug crashes; possibly other uses. - Allow(SYSCALL(nanosleep)); - - // For the crash reporter: -#if SYSCALL_EXISTS(sigprocmask) - Allow(SYSCALL(sigprocmask)); -#endif - Allow(SYSCALL(rt_sigprocmask)); -#if SYSCALL_EXISTS(sigaction) - Allow(SYSCALL(sigaction)); -#endif - Allow(SYSCALL(rt_sigaction)); - Allow(SYSCALL(pipe)); - Allow(SYSCALL_WITH_ARG(tgkill, 0, uint32_t(getpid()))); - Allow(SYSCALL_WITH_ARG(prctl, 0, PR_SET_DUMPABLE)); - - // Note for when GMP is supported on an ARM platform: Add whichever - // of the ARM-specific syscalls are needed for this type of process. - - Allow(SYSCALL(epoll_ctl)); - Allow(SYSCALL(exit)); - Allow(SYSCALL(exit_group)); -} #endif // MOZ_GMP_SANDBOX -SandboxFilter::SandboxFilter(const sock_fprog** aStored, SandboxType aType, - bool aVerbose) - : mStored(aStored) -{ - MOZ_ASSERT(*mStored == nullptr); - std::vector<struct sock_filter> filterVec; - SandboxFilterImpl *impl; - - switch (aType) { - case kSandboxContentProcess: -#ifdef MOZ_CONTENT_SANDBOX - impl = new SandboxFilterImplContent(); -#else - MOZ_CRASH("Content process sandboxing not supported in this build!"); -#endif - break; - case kSandboxMediaPlugin: -#ifdef MOZ_GMP_SANDBOX - impl = new SandboxFilterImplGMP(); -#else - MOZ_CRASH("Gecko Media Plugin process sandboxing not supported in this" - " build!"); -#endif - break; - default: - MOZ_CRASH("Nonexistent sandbox type!"); - } - impl->Build(); - impl->Compile(&filterVec, aVerbose); - delete impl; - - mProg = new sock_fprog; - mProg->len = filterVec.size(); - mProg->filter = mFilter = new sock_filter[mProg->len]; - for (size_t i = 0; i < mProg->len; ++i) { - mFilter[i] = filterVec[i]; - } - *mStored = mProg; } - -SandboxFilter::~SandboxFilter() -{ - *mStored = nullptr; - delete[] mFilter; - delete mProg; -} - -}
--- a/security/sandbox/linux/SandboxFilter.h +++ b/security/sandbox/linux/SandboxFilter.h @@ -2,34 +2,35 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #ifndef mozilla_SandboxFilter_h #define mozilla_SandboxFilter_h -struct sock_fprog; -struct sock_filter; +#include "mozilla/Atomics.h" +#include "mozilla/UniquePtr.h" + +namespace sandbox { +namespace bpf_dsl { +class Policy; +} +} namespace mozilla { -enum SandboxType { - kSandboxContentProcess, - kSandboxMediaPlugin +#ifdef MOZ_CONTENT_SANDBOX +UniquePtr<sandbox::bpf_dsl::Policy> GetContentSandboxPolicy(); +#endif + +#ifdef MOZ_GMP_SANDBOX +struct SandboxOpenedFile { + const char *mPath; + Atomic<int> mFd; }; -class SandboxFilter { - sock_filter *mFilter; - sock_fprog *mProg; - const sock_fprog **mStored; -public: - // RAII: on construction, builds the filter and stores it in the - // provided variable (with optional logging); on destruction, frees - // the filter and nulls out the pointer. - SandboxFilter(const sock_fprog** aStored, SandboxType aBox, - bool aVerbose = false); - ~SandboxFilter(); -}; +UniquePtr<sandbox::bpf_dsl::Policy> GetMediaSandboxPolicy(SandboxOpenedFile* aPlugin); +#endif } // namespace mozilla #endif
new file mode 100644 --- /dev/null +++ b/security/sandbox/linux/SandboxFilterUtil.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SandboxFilterUtil.h" + +#ifndef ANDROID +#include <linux/ipc.h> +#endif +#include <linux/net.h> + +#include "mozilla/UniquePtr.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" + +#ifndef SYS_ACCEPT4 +// Android's kernel headers don't define these. +#define SYS_ACCEPT4 18 +#define SYS_RECVMMSG 19 +#define SYS_SENDMMSG 20 +#endif + +using namespace sandbox::bpf_dsl; +#define CASES SANDBOX_BPF_DSL_CASES + +namespace mozilla { + +sandbox::bpf_dsl::ResultExpr +SandboxPolicyBase::EvaluateSyscall(int aSysno) const { + switch (aSysno) { +#ifdef __NR_socketcall + case __NR_socketcall: { + Arg<int> call(0); + UniquePtr<Caser<int>> acc(new Caser<int>(Switch(call))); + for (int i = SYS_SOCKET; i <= SYS_SENDMMSG; ++i) { + auto thisCase = EvaluateSocketCall(i); + // Optimize out cases that are equal to the default. + if (thisCase) { + acc.reset(new Caser<int>(acc->Case(i, *thisCase))); + } + } + return acc->Default(InvalidSyscall()); + } +#ifndef ANDROID + case __NR_ipc: { + Arg<int> callAndVersion(0); + auto call = callAndVersion & 0xFFFF; + UniquePtr<Caser<int>> acc(new Caser<int>(Switch(call))); + for (int i = SEMOP; i <= DIPC; ++i) { + auto thisCase = EvaluateIpcCall(i); + // Optimize out cases that are equal to the default. + if (thisCase) { + acc.reset(new Caser<int>(acc->Case(i, *thisCase))); + } + } + return acc->Default(InvalidSyscall()); + } +#endif // ANDROID +#else // __NR_socketcall +#define DISPATCH_SOCKETCALL(sysnum, socketnum) \ + case sysnum: \ + return EvaluateSocketCall(socketnum).valueOr(InvalidSyscall()) + DISPATCH_SOCKETCALL(__NR_socket, SYS_SOCKET); + DISPATCH_SOCKETCALL(__NR_bind, SYS_BIND); + DISPATCH_SOCKETCALL(__NR_connect, SYS_CONNECT); + DISPATCH_SOCKETCALL(__NR_listen, SYS_LISTEN); + DISPATCH_SOCKETCALL(__NR_accept, SYS_ACCEPT); + DISPATCH_SOCKETCALL(__NR_getsockname, SYS_GETSOCKNAME); + DISPATCH_SOCKETCALL(__NR_getpeername, SYS_GETPEERNAME); + DISPATCH_SOCKETCALL(__NR_socketpair, SYS_SOCKETPAIR); +#ifdef __NR_send + DISPATCH_SOCKETCALL(__NR_send, SYS_SEND); + DISPATCH_SOCKETCALL(__NR_recv, SYS_RECV); +#endif // __NR_send + DISPATCH_SOCKETCALL(__NR_sendto, SYS_SENDTO); + DISPATCH_SOCKETCALL(__NR_recvfrom, SYS_RECVFROM); + DISPATCH_SOCKETCALL(__NR_shutdown, SYS_SHUTDOWN); + DISPATCH_SOCKETCALL(__NR_setsockopt, SYS_SETSOCKOPT); + DISPATCH_SOCKETCALL(__NR_getsockopt, SYS_GETSOCKOPT); + DISPATCH_SOCKETCALL(__NR_sendmsg, SYS_SENDMSG); + DISPATCH_SOCKETCALL(__NR_recvmsg, SYS_RECVMSG); + DISPATCH_SOCKETCALL(__NR_accept4, SYS_ACCEPT4); + DISPATCH_SOCKETCALL(__NR_recvmmsg, SYS_RECVMMSG); + DISPATCH_SOCKETCALL(__NR_sendmmsg, SYS_SENDMMSG); +#undef DISPATCH_SOCKETCALL +#ifndef ANDROID +#define DISPATCH_SYSVCALL(sysnum, ipcnum) \ + case sysnum: \ + return EvaluateIpcCall(ipcnum).valueOr(InvalidSyscall()) + DISPATCH_SYSVCALL(__NR_semop, SEMOP); + DISPATCH_SYSVCALL(__NR_semget, SEMGET); + DISPATCH_SYSVCALL(__NR_semctl, SEMCTL); + DISPATCH_SYSVCALL(__NR_semtimedop, SEMTIMEDOP); + DISPATCH_SYSVCALL(__NR_msgsnd, MSGSND); + DISPATCH_SYSVCALL(__NR_msgrcv, MSGRCV); + DISPATCH_SYSVCALL(__NR_msgget, MSGGET); + DISPATCH_SYSVCALL(__NR_msgctl, MSGCTL); + DISPATCH_SYSVCALL(__NR_shmat, SHMAT); + DISPATCH_SYSVCALL(__NR_shmdt, SHMDT); + DISPATCH_SYSVCALL(__NR_shmget, SHMGET); + DISPATCH_SYSVCALL(__NR_shmctl, SHMCTL); +#undef DISPATCH_SYSVCALL +#endif // ANDROID +#endif // __NR_socketcall + default: + return InvalidSyscall(); + } +} + +}
new file mode 100644 --- /dev/null +++ b/security/sandbox/linux/SandboxFilterUtil.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_SandboxFilterUtil_h +#define mozilla_SandboxFilterUtil_h + +// This header file exists to hold helper code for SandboxFilter.cpp, +// to make that file easier to read for anyone trying to understand +// the filter policy. It's mostly about smoothing out differences +// between different Linux architectures. + +#include "mozilla/Maybe.h" +#include "sandbox/linux/services/linux_syscalls.h" +#include "sandbox/linux/bpf_dsl/policy.h" + +namespace mozilla { + +// This class handles syscalls for BSD socket and SysV IPC operations. +// On 32-bit x86 they're multiplexed via socketcall(2) and ipc(2), +// respectively; on most other architectures they're individual system +// calls. It translates the syscalls into socketcall/ipc selector +// values, because those are defined (even if not used) for all +// architectures. +// +// This EvaluateSyscall() routine always returns InvalidSyscall() for +// everything else. It's assumed that subclasses will be implementing +// a whitelist policy, so they can handle what they're whitelisting +// and then defer to this class in the default case. +class SandboxPolicyBase : public sandbox::bpf_dsl::Policy +{ +public: + using ResultExpr = sandbox::bpf_dsl::ResultExpr; + + virtual ResultExpr EvaluateSyscall(int aSysno) const override; + virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const { + return Nothing(); + } +#ifndef ANDROID + // Android doesn't use SysV IPC (and doesn't define the selector + // constants in its headers), so this isn't implemented there. + virtual Maybe<ResultExpr> EvaluateIpcCall(int aCall) const { + return Nothing(); + } +#endif + +#ifdef __NR_socketcall + // socketcall(2) takes the actual call's arguments via a pointer, so + // seccomp-bpf can't inspect them; ipc(2) takes them at different indices. + static const bool kSocketCallHasArgs = false; + static const bool kIpcCallNormalArgs = false; +#else + // Otherwise, the bpf_dsl Arg<> class can be used normally. + static const bool kSocketCallHasArgs = true; + static const bool kIpcCallNormalArgs = true; +#endif +}; + +} // namespace mozilla + +// "Machine independent" pseudo-syscall numbers, to deal with arch +// dependencies. (Most 32-bit archs started with 32-bit off_t; older +// archs started with 16-bit uid_t/gid_t; 32-bit registers can't hold +// a 64-bit offset for mmap; and so on.) +// +// For some of these, the "old" syscalls are also in use in some +// cases; see, e.g., the handling of RT vs. non-RT signal syscalls. + +#ifdef __NR_mmap2 +#define CASES_FOR_mmap case __NR_mmap2 +#else +#define CASES_FOR_mmap case __NR_mmap +#endif + +#ifdef __NR_getuid32 +#define CASES_FOR_getuid case __NR_getuid32 +#define CASES_FOR_getgid case __NR_getgid32 +#define CASES_FOR_geteuid case __NR_geteuid32 +#define CASES_FOR_getegid case __NR_getegid32 +#define CASES_FOR_getresuid case __NR_getresuid32 +#define CASES_FOR_getresgid case __NR_getresgid32 +// The set*id syscalls are omitted; we'll probably never need to allow them. +#else +#define CASES_FOR_getuid case __NR_getuid +#define CASES_FOR_getgid case __NR_getgid +#define CASES_FOR_geteuid case __NR_geteuid +#define CASES_FOR_getegid case __NR_getegid +#define CASES_FOR_getresuid case __NR_getresuid +#define CASES_FOR_getresgid case __NR_getresgid +#endif + +#ifdef __NR_stat64 +#define CASES_FOR_stat case __NR_stat64 +#define CASES_FOR_lstat case __NR_lstat64 +#define CASES_FOR_fstat case __NR_fstat64 +#define CASES_FOR_fstatat case __NR_fstatat64 +#define CASES_FOR_statfs case __NR_statfs64 +#define CASES_FOR_fcntl case __NR_fcntl64 +// We're using the 32-bit version on 32-bit desktop for some reason. +#define CASES_FOR_getdents case __NR_getdents64: case __NR_getdents +// FIXME: we might not need the compat cases for these on non-Android: +#define CASES_FOR_lseek case __NR_lseek: case __NR__llseek +#define CASES_FOR_ftruncate case __NR_ftruncate: case __NR_ftruncate64 +#else +#define CASES_FOR_stat case __NR_stat +#define CASES_FOR_lstat case __NR_lstat +#define CASES_FOR_fstatat case __NR_newfstatat +#define CASES_FOR_fstat case __NR_fstat +#define CASES_FOR_statfs case __NR_statfs +#define CASES_FOR_fcntl case __NR_fcntl +#define CASES_FOR_getdents case __NR_getdents +#define CASES_FOR_lseek case __NR_lseek +#define CASES_FOR_ftruncate case __NR_ftruncate +#endif + +#ifdef __NR_sigprocmask +#define CASES_FOR_sigprocmask case __NR_sigprocmask: case __NR_rt_sigprocmask +#define CASES_FOR_sigaction case __NR_sigaction: case __NR_rt_sigaction +#define CASES_FOR_sigreturn case __NR_sigreturn: case __NR_rt_sigreturn +#else +#define CASES_FOR_sigprocmask case __NR_rt_sigprocmask +#define CASES_FOR_sigaction case __NR_rt_sigaction +#define CASES_FOR_sigreturn case __NR_rt_sigreturn +#endif + +#ifdef __NR__newselect +#define CASES_FOR_select case __NR__newselect +#else +#define CASES_FOR_select case __NR_select +#endif + +#ifdef __NR_ugetrlimit +#define CASES_FOR_getrlimit case __NR_ugetrlimit +#else +#define CASES_FOR_getrlimit case __NR_getrlimit +#endif + +#endif // mozilla_SandboxFilterUtil_h
--- a/security/sandbox/linux/moz.build +++ b/security/sandbox/linux/moz.build @@ -42,28 +42,30 @@ SOURCES += [ '../chromium/base/threading/thread_collision_warner.cc', '../chromium/base/threading/thread_id_name_manager.cc', '../chromium/base/threading/thread_local_posix.cc', '../chromium/base/threading/thread_restrictions.cc', '../chromium/base/time/time.cc', '../chromium/base/time/time_posix.cc', '../chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc', '../chromium/sandbox/linux/bpf_dsl/dump_bpf.cc', + '../chromium/sandbox/linux/bpf_dsl/policy.cc', '../chromium/sandbox/linux/bpf_dsl/policy_compiler.cc', '../chromium/sandbox/linux/seccomp-bpf/basicblock.cc', '../chromium/sandbox/linux/seccomp-bpf/codegen.cc', '../chromium/sandbox/linux/seccomp-bpf/die.cc', '../chromium/sandbox/linux/seccomp-bpf/errorcode.cc', '../chromium/sandbox/linux/seccomp-bpf/syscall.cc', '../chromium/sandbox/linux/seccomp-bpf/syscall_iterator.cc', + '../chromium/sandbox/linux/seccomp-bpf/trap.cc', 'LinuxCapabilities.cpp', 'Sandbox.cpp', - 'SandboxAssembler.cpp', 'SandboxChroot.cpp', 'SandboxFilter.cpp', + 'SandboxFilterUtil.cpp', 'SandboxUtil.cpp', ] # gcc lto likes to put the top level asm in syscall.cc in a different partition # from the function using it which breaks the build. Work around that by # forcing there to be only one partition. if '-flto' in CONFIG['OS_CXXFLAGS'] and not CONFIG['CLANG_CXX']: LDFLAGS += ['--param lto-partitions=1']
--- a/tools/profiler/ProfileEntry.cpp +++ b/tools/profiler/ProfileEntry.cpp @@ -395,22 +395,16 @@ UniqueStacks::Stack UniqueStacks::BeginS UniqueStacks::UniqueStacks(JSRuntime* aRuntime) : mRuntime(aRuntime) , mFrameCount(0) { mFrameTableWriter.StartBareList(); mStackTableWriter.StartBareList(); } -UniqueStacks::~UniqueStacks() -{ - mFrameTableWriter.EndBareList(); - mStackTableWriter.EndBareList(); -} - uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack) { uint32_t index; if (mStackToIndexMap.Get(aStack, &index)) { MOZ_ASSERT(index < mStackToIndexMap.Count()); return index; } @@ -457,24 +451,26 @@ uint32_t UniqueStacks::LookupJITFrameDep return 0; } void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth) { mJITFrameDepthMap.Put(aAddr, depth); } -void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter) const +void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter) { - aWriter.Splice(mFrameTableWriter.WriteFunc()); + mFrameTableWriter.EndBareList(); + aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc()); } -void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter) const +void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter) { - aWriter.Splice(mStackTableWriter.WriteFunc()); + mStackTableWriter.EndBareList(); + aWriter.TakeAndSplice(mStackTableWriter.WriteFunc()); } void UniqueStacks::StreamStack(const StackKey& aStack) { // Schema: // [prefix, frame] mStackTableWriter.StartArrayElement();
--- a/tools/profiler/ProfileEntry.h +++ b/tools/profiler/ProfileEntry.h @@ -82,22 +82,18 @@ private: class UniqueJSONStrings { public: UniqueJSONStrings() { mStringTableWriter.StartBareList(); } - ~UniqueJSONStrings() { - mStringTableWriter.EndBareList(); - } - - void SpliceStringTableElements(SpliceableJSONWriter& aWriter) const { - aWriter.Splice(mStringTableWriter.WriteFunc()); + void SpliceStringTableElements(SpliceableJSONWriter& aWriter) { + aWriter.TakeAndSplice(mStringTableWriter.WriteFunc()); } void WriteProperty(mozilla::JSONWriter& aWriter, const char* aName, const char* aStr) { aWriter.IntProperty(aName, GetOrAddIndex(aStr)); } void WriteElement(mozilla::JSONWriter& aWriter, const char* aStr) { aWriter.IntElement(GetOrAddIndex(aStr)); @@ -188,23 +184,22 @@ public: uint32_t GetOrAddIndex() const; private: UniqueStacks& mUniqueStacks; StackKey mStack; }; explicit UniqueStacks(JSRuntime* aRuntime); - ~UniqueStacks(); Stack BeginStack(const OnStackFrameKey& aRoot); uint32_t LookupJITFrameDepth(void* aAddr); void AddJITFrameDepth(void* aAddr, unsigned depth); - void SpliceFrameTableElements(SpliceableJSONWriter& aWriter) const; - void SpliceStackTableElements(SpliceableJSONWriter& aWriter) const; + void SpliceFrameTableElements(SpliceableJSONWriter& aWriter); + void SpliceStackTableElements(SpliceableJSONWriter& aWriter); private: uint32_t GetOrAddFrameIndex(const OnStackFrameKey& aFrame); uint32_t GetOrAddStackIndex(const StackKey& aStack); void StreamFrame(const OnStackFrameKey& aFrame); void StreamStack(const StackKey& aStack); public:
--- a/tools/profiler/ProfileJSONWriter.cpp +++ b/tools/profiler/ProfileJSONWriter.cpp @@ -53,36 +53,63 @@ ChunkedJSONWriteFunc::CopyData() const memcpy(ptr, mChunkList[i].get(), len); ptr += len; } *ptr = '\0'; return c; } void +ChunkedJSONWriteFunc::Take(ChunkedJSONWriteFunc&& aOther) +{ + for (size_t i = 0; i < aOther.mChunkList.length(); i++) { + MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i])); + MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(aOther.mChunkList[i]))); + } + mChunkPtr = mChunkList.back().get() + mChunkLengths.back(); + mChunkEnd = mChunkPtr; + aOther.mChunkPtr = nullptr; + aOther.mChunkEnd = nullptr; + aOther.mChunkList.clear(); + aOther.mChunkLengths.clear(); +} + +void ChunkedJSONWriteFunc::AllocChunk(size_t aChunkSize) { MOZ_ASSERT(mChunkLengths.length() == mChunkList.length()); mozilla::UniquePtr<char[]> newChunk = mozilla::MakeUnique<char[]>(aChunkSize); mChunkPtr = newChunk.get(); mChunkEnd = mChunkPtr + aChunkSize; *mChunkPtr = '\0'; MOZ_ALWAYS_TRUE(mChunkLengths.append(0)); MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(newChunk))); } void -SpliceableJSONWriter::Splice(const ChunkedJSONWriteFunc* aFunc) +SpliceableJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc) { Separator(); for (size_t i = 0; i < aFunc->mChunkList.length(); i++) { WriteFunc()->Write(aFunc->mChunkList[i].get()); } + aFunc->mChunkPtr = nullptr; + aFunc->mChunkEnd = nullptr; + aFunc->mChunkList.clear(); + aFunc->mChunkLengths.clear(); mNeedComma[mDepth] = true; } void SpliceableJSONWriter::Splice(const char* aStr) { Separator(); WriteFunc()->Write(aStr); mNeedComma[mDepth] = true; } + +void +SpliceableChunkedJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc) +{ + Separator(); + WriteFunc()->Take(mozilla::Move(*aFunc)); + mNeedComma[mDepth] = true; +}
--- a/tools/profiler/ProfileJSONWriter.h +++ b/tools/profiler/ProfileJSONWriter.h @@ -24,18 +24,26 @@ class ChunkedJSONWriteFunc : public mozi { public: friend class SpliceableJSONWriter; ChunkedJSONWriteFunc() { AllocChunk(kChunkSize); } + bool IsEmpty() const { + MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd && + mChunkList.length() == 0 && + mChunkLengths.length() == 0); + return !mChunkPtr; + } + void Write(const char* aStr) override; mozilla::UniquePtr<char[]> CopyData() const; + void Take(ChunkedJSONWriteFunc&& aOther); private: void AllocChunk(size_t aChunkSize); static const size_t kChunkSize = 4096 * 512; // Pointer for writing inside the current chunk. // @@ -88,23 +96,31 @@ public: void NullElements(uint32_t aCount) { for (uint32_t i = 0; i < aCount; i++) { NullElement(); } } void Splice(const ChunkedJSONWriteFunc* aFunc); void Splice(const char* aStr); + + // Takes the chunks from aFunc and write them. If move is not possible + // (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its + // storage cleared. + virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc); }; class SpliceableChunkedJSONWriter : public SpliceableJSONWriter { public: explicit SpliceableChunkedJSONWriter() : SpliceableJSONWriter(mozilla::MakeUnique<ChunkedJSONWriteFunc>()) { } ChunkedJSONWriteFunc* WriteFunc() const { return static_cast<ChunkedJSONWriteFunc*>(JSONWriter::WriteFunc()); } + + // Adopts the chunks from aFunc without copying. + virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc) override; }; #endif // PROFILEJSONWRITER_H
--- a/widget/ContentCache.cpp +++ b/widget/ContentCache.cpp @@ -140,24 +140,25 @@ ContentCache::AssignContent(const Conten mSelection = aOther.mSelection; mCaret = aOther.mCaret; mTextRectArray = aOther.mTextRectArray; mEditorRect = aOther.mEditorRect; MOZ_LOG(sContentCacheLog, LogLevel::Info, ("ContentCache: 0x%p (mIsChrome=%s) AssignContent(aNotification=%s), " "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, " - "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s }, " + "mWritingMode=%s, mAnchorCharRect=%s, mFocusCharRect=%s, mRect=%s }, " "mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ mStart=%u, " "mRects.Length()=%u }, mEditorRect=%s", this, GetBoolName(mIsChrome), GetNotificationName(aNotification), mText.Length(), mSelection.mAnchor, mSelection.mFocus, GetWritingModeName(mSelection.mWritingMode).get(), GetRectText(mSelection.mAnchorCharRect).get(), - GetRectText(mSelection.mFocusCharRect).get(), mCaret.mOffset, + GetRectText(mSelection.mFocusCharRect).get(), + GetRectText(mSelection.mRect).get(), mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart, mTextRectArray.mRects.Length(), GetRectText(mEditorRect).get())); } void ContentCache::Clear() { MOZ_LOG(sContentCacheLog, LogLevel::Info, @@ -590,16 +591,17 @@ ContentCache::CacheTextRects(nsIWidget* // CacheTextRects() must be called in content process. if (NS_WARN_IF(mIsChrome)) { return false; } mTextRectArray.Clear(); mSelection.mAnchorCharRect.SetEmpty(); mSelection.mFocusCharRect.SetEmpty(); + mSelection.mRect.SetEmpty(); if (NS_WARN_IF(!mSelection.IsValid())) { return false; } // Retrieve text rects in composition string if there is. nsRefPtr<TextComposition> textComposition = IMEStateManager::GetTextCompositionFor(aWidget); @@ -648,25 +650,42 @@ ContentCache::CacheTextRects(nsIWidget* MOZ_LOG(sContentCacheLog, LogLevel::Error, ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, " "couldn't retrieve text rect at focus of selection (%u)", this, GetBoolName(mIsChrome), mSelection.mFocus)); } mSelection.mFocusCharRect = charRect; } + if (!mSelection.Collapsed()) { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetQueryContentEvent textRect(true, NS_QUERY_TEXT_RECT, aWidget); + textRect.InitForQueryTextRect(mSelection.StartOffset(), + mSelection.Length()); + aWidget->DispatchEvent(&textRect, status); + if (NS_WARN_IF(!textRect.mSucceeded)) { + MOZ_LOG(sContentCacheLog, LogLevel::Error, + ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), FAILED, " + "couldn't retrieve text rect of whole selected text", + this, GetBoolName(mIsChrome))); + } else { + mSelection.mRect = textRect.mReply.mRect; + } + } + MOZ_LOG(sContentCacheLog, LogLevel::Info, ("ContentCache: 0x%p (mIsChrome=%s) CacheTextRects(), Succeeded, " "mText.Length()=%u, mTextRectArray={ mStart=%u, mRects.Length()=%u }, " "mSelection={ mAnchor=%u, mAnchorCharRect=%s, mFocus=%u, " - "mFocusCharRect=%s }", + "mFocusCharRect=%s, mRect=%s }", this, GetBoolName(mIsChrome), mText.Length(), mTextRectArray.mStart, mTextRectArray.mRects.Length(), mSelection.mAnchor, GetRectText(mSelection.mAnchorCharRect).get(), mSelection.mFocus, - GetRectText(mSelection.mFocusCharRect).get())); + GetRectText(mSelection.mFocusCharRect).get(), + GetRectText(mSelection.mRect).get())); return true; } void ContentCache::SetSelection(nsIWidget* aWidget, uint32_t aStartOffset, uint32_t aLength, bool aReversed, @@ -736,16 +755,23 @@ ContentCache::GetUnionTextRects(uint32_t MOZ_LOG(sContentCacheLog, LogLevel::Info, ("ContentCache: 0x%p (mIsChrome=%s) GetUnionTextRects(aOffset=%u, " "aLength=%u), mTextRectArray={ mStart=%u, mRects.Length()=%u }, " "mSelection={ mAnchor=%u, mFocus=%u }", this, GetBoolName(mIsChrome), aOffset, aLength, mTextRectArray.mStart, mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus)); + if (!mSelection.Collapsed() && + aOffset == mSelection.StartOffset() && aLength == mSelection.Length()) { + NS_WARN_IF(mSelection.mRect.IsEmpty()); + aUnionTextRect = mSelection.mRect; + return !aUnionTextRect.IsEmpty(); + } + if (aLength == 1) { if (aOffset == mSelection.mAnchor) { NS_WARN_IF(mSelection.mAnchorCharRect.IsEmpty()); aUnionTextRect = mSelection.mAnchorCharRect; return !aUnionTextRect.IsEmpty(); } if (aOffset == mSelection.mFocus) { NS_WARN_IF(mSelection.mFocusCharRect.IsEmpty());
--- a/widget/ContentCache.h +++ b/widget/ContentCache.h @@ -165,28 +165,32 @@ private: uint32_t mFocus; WritingMode mWritingMode; // Character rects at next character of mAnchor and mFocus. LayoutDeviceIntRect mAnchorCharRect; LayoutDeviceIntRect mFocusCharRect; + // Whole rect of selected text. This is empty if the selection is collapsed. + LayoutDeviceIntRect mRect; + Selection() : mAnchor(UINT32_MAX) , mFocus(UINT32_MAX) { } void Clear() { mAnchor = mFocus = UINT32_MAX; mWritingMode = WritingMode(); mAnchorCharRect.SetEmpty(); mFocusCharRect.SetEmpty(); + mRect.SetEmpty(); } bool IsValid() const { return mAnchor != UINT32_MAX && mFocus != UINT32_MAX; } bool Collapsed() const {
--- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -802,31 +802,33 @@ struct ParamTraits<mozilla::ContentCache static void Write(Message* aMsg, const paramType& aParam) { WriteParam(aMsg, aParam.mText); WriteParam(aMsg, aParam.mSelection.mAnchor); WriteParam(aMsg, aParam.mSelection.mFocus); WriteParam(aMsg, aParam.mSelection.mWritingMode); WriteParam(aMsg, aParam.mSelection.mAnchorCharRect); WriteParam(aMsg, aParam.mSelection.mFocusCharRect); + WriteParam(aMsg, aParam.mSelection.mRect); WriteParam(aMsg, aParam.mCaret.mOffset); WriteParam(aMsg, aParam.mCaret.mRect); WriteParam(aMsg, aParam.mTextRectArray.mStart); WriteParam(aMsg, aParam.mTextRectArray.mRects); WriteParam(aMsg, aParam.mEditorRect); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) { return ReadParam(aMsg, aIter, &aResult->mText) && ReadParam(aMsg, aIter, &aResult->mSelection.mAnchor) && ReadParam(aMsg, aIter, &aResult->mSelection.mFocus) && ReadParam(aMsg, aIter, &aResult->mSelection.mWritingMode) && ReadParam(aMsg, aIter, &aResult->mSelection.mAnchorCharRect) && ReadParam(aMsg, aIter, &aResult->mSelection.mFocusCharRect) && + ReadParam(aMsg, aIter, &aResult->mSelection.mRect) && ReadParam(aMsg, aIter, &aResult->mCaret.mOffset) && ReadParam(aMsg, aIter, &aResult->mCaret.mRect) && ReadParam(aMsg, aIter, &aResult->mTextRectArray.mStart) && ReadParam(aMsg, aIter, &aResult->mTextRectArray.mRects) && ReadParam(aMsg, aIter, &aResult->mEditorRect); } };
--- a/xpcom/base/moz.build +++ b/xpcom/base/moz.build @@ -60,19 +60,16 @@ EXPORTS += [ 'nsTraceRefcnt.h', 'nsWeakPtr.h', ] if CONFIG['OS_ARCH'] == 'WINNT': EXPORTS += [ 'nsWindowsHelpers.h', ] - if CONFIG['MOZ_DEBUG']: - EXPORTS += ['pure.h'] - SOURCES += ['pure_api.c'] EXPORTS.mozilla += [ 'AvailableMemoryTracker.h', 'ClearOnShutdown.h', 'CountingAllocatorBase.h', 'CycleCollectedJSRuntime.h', 'Debug.h', 'DebuggerOnGCRunnable.h',
deleted file mode 100644 --- a/xpcom/base/pure.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Header file of Pure API function declarations. - * - * Explicitly no copyright. - * You may recompile and redistribute these definitions as required. - * - * Version 1.0 - */ - -#if defined(c_plusplus) || defined(__cplusplus) -extern "C" { -#endif - -#define PURE_H_VERSION 1 - -////////////////////////////// -// API's Specific to Purify // -////////////////////////////// - -// TRUE when Purify is running. -int __cdecl PurifyIsRunning(void) ; -// -// Print a string to the viewer. -// -int __cdecl PurePrintf(const char *fmt, ...) ; -int __cdecl PurifyPrintf(const char *fmt, ...) ; -// -// Purify functions for leak and memory-in-use functionalty. -// -int __cdecl PurifyNewInuse(void) ; -int __cdecl PurifyAllInuse(void) ; -int __cdecl PurifyClearInuse(void) ; -int __cdecl PurifyNewLeaks(void) ; -int __cdecl PurifyAllLeaks(void) ; -int __cdecl PurifyClearLeaks(void) ; -// -// Purify functions for handle leakage. -// -int __cdecl PurifyAllHandlesInuse(void) ; -int __cdecl PurifyNewHandlesInuse(void) ; -// -// Functions that tell you about the state of memory. -// -int __cdecl PurifyDescribe(void *addr) ; -int __cdecl PurifyWhatColors(void *addr, int size) ; -// -// Functions to test the state of memory. If the memory is not -// accessible, an error is signaled just as if there were a memory -// reference and the function returns false. -// -int __cdecl PurifyAssertIsReadable(const void *addr, int size) ; -int __cdecl PurifyAssertIsWritable(const void *addr, int size) ; -// -// Functions to test the state of memory. If the memory is not -// accessible, these functions return false. No error is signaled. -// -int __cdecl PurifyIsReadable(const void *addr, int size) ; -int __cdecl PurifyIsWritable(const void *addr, int size) ; -int __cdecl PurifyIsInitialized(const void *addr, int size) ; -// -// Functions to set the state of memory. -// -void __cdecl PurifyMarkAsInitialized(void *addr, int size) ; -void __cdecl PurifyMarkAsUninitialized(void *addr, int size) ; -// -// Functions to do late detection of ABWs, FMWs, IPWs. -// -#define PURIFY_HEAP_CRT 0xfffffffe -#define PURIFY_HEAP_ALL 0xfffffffd -#define PURIFY_HEAP_BLOCKS_LIVE 0x80000000 -#define PURIFY_HEAP_BLOCKS_DEFERRED_FREE 0x40000000 -#define PURIFY_HEAP_BLOCKS_ALL (PURIFY_HEAP_BLOCKS_LIVE|PURIFY_HEAP_BLOCKS_DEFERRED_FREE) -int __cdecl PurifyHeapValidate(unsigned int hHeap, unsigned int dwFlags, const void *addr) ; -int __cdecl PurifySetLateDetectScanCounter(int counter); -int __cdecl PurifySetLateDetectScanInterval(int seconds); - - -//////////////////////////////// -// API's Specific to Quantify // -//////////////////////////////// - -// TRUE when Quantify is running. -int __cdecl QuantifyIsRunning(void) ; - -// -// Functions for controlling collection -// -int __cdecl QuantifyDisableRecordingData(void) ; -int __cdecl QuantifyStartRecordingData(void) ; -int __cdecl QuantifyStopRecordingData(void) ; -int __cdecl QuantifyClearData(void) ; -int __cdecl QuantifyIsRecordingData(void) ; - -// Add a comment to the dataset -int __cdecl QuantifyAddAnnotation(char *) ; - -// Save the current data, creating a "checkpoint" dataset -int __cdecl QuantifySaveData(void) ; - -//////////////////////////////// -// API's Specific to Coverage // -//////////////////////////////// - -// TRUE when Coverage is running. -int __cdecl CoverageIsRunning(void) ; -// -// Functions for controlling collection -// -int __cdecl CoverageDisableRecordingData(void) ; -int __cdecl CoverageStartRecordingData(void) ; -int __cdecl CoverageStopRecordingData(void) ; -int __cdecl CoverageClearData(void) ; -int __cdecl CoverageIsRecordingData(void) ; -// Add a comment to the dataset -int __cdecl CoverageAddAnnotation(char *) ; - -// Save the current data, creating a "checkpoint" dataset -int __cdecl CoverageSaveData(void) ; - - - - -//////////////////////////////// -// API's Specific to Purelock // -//////////////////////////////// - -// TRUE when Purelock is running. -int __cdecl PurelockIsRunning(void) ; - - - - -#if defined(c_plusplus) || defined(__cplusplus) -} -#endif
deleted file mode 100644 --- a/xpcom/base/pure_api.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Header file of Pure API function declarations. - * - * Explicitly no copyright. - * You may recompile and redistribute these definitions as required. - * - * NOTE1: In some situations when compiling with MFC, you should - * enable the setting 'Not using precompiled headers' in Visual C++ - * to avoid a compiler diagnostic. - * - * NOTE2: This file works through the use of deep magic. Calls to functions - * in this file are replaced with calls into the OCI runtime system - * when an instrumented version of this program is run. - * - * NOTE3: The static vars avoidGy_n (where n is a unique number) are used - * to prevent optimizing the functions away when compiler option - * /Gy is set. This is needed so that NOTE2 works properly. - */ -static int avoidGy_1; -static int avoidGy_2; -static int avoidGy_3; -static int avoidGy_4; -static int avoidGy_5; -static int avoidGy_6; -static int avoidGy_7; -static int avoidGy_8; -static int avoidGy_9; -static int avoidGy_10; -static int avoidGy_11; -static int avoidGy_12; -static int avoidGy_13; -static int avoidGy_14; -static int avoidGy_15; -static int avoidGy_16; -static int avoidGy_17; -static int avoidGy_18; -static int avoidGy_19; -static int avoidGy_20; -static int avoidGy_21; -static int avoidGy_22; -static int avoidGy_23; -static int avoidGy_24; -static int avoidGy_25; -static int avoidGy_26; -static int avoidGy_27; -static int avoidGy_28; -static int avoidGy_29; -static int avoidGy_30; -static int avoidGy_31; -static int avoidGy_32; -static int avoidGy_33; -static int avoidGy_34; -static int avoidGy_35; -static int avoidGy_36; -static int avoidGy_37; -static int avoidGy_38; -static int avoidGy_39; -static int avoidGy_40; -static int avoidGy_41; -static int avoidGy_42; -static int avoidGy_43; -static int avoidGy_44; -static int avoidGy_45; -static int avoidGy_46; -static int avoidGy_47; -static int avoidGy_48; -static int avoidGy_49; -static int avoidGy_50; -static int avoidGy_51; -static int avoidGy_52; -static int avoidGy_53; -static int avoidGy_54; -static int avoidGy_55; -static int avoidGy_56; -static int avoidGy_57; -static int avoidGy_58; -static int avoidGy_59; -static int avoidGy_60; -static int avoidGy_PL_01; -__declspec(dllexport) int __cdecl PurePrintf(const char *fmt, ...) { avoidGy_1++; fmt; return 0; } -__declspec(dllexport) int __cdecl PurifyIsRunning(void) { avoidGy_2++; return 0; } -__declspec(dllexport) int __cdecl PurifyPrintf(const char *fmt, ...) { avoidGy_3++; fmt; return 0; } -__declspec(dllexport) int __cdecl PurifyNewInuse(void) { avoidGy_4++; return 0; } -__declspec(dllexport) int __cdecl PurifyAllInuse(void) { avoidGy_5++; return 0; } -__declspec(dllexport) int __cdecl PurifyClearInuse(void) { avoidGy_6++; return 0; } -__declspec(dllexport) int __cdecl PurifyNewLeaks(void) { avoidGy_7++; return 0; } -__declspec(dllexport) int __cdecl PurifyAllLeaks(void) { avoidGy_8++; return 0; } -__declspec(dllexport) int __cdecl PurifyClearLeaks(void) { avoidGy_9++; return 0; } -__declspec(dllexport) int __cdecl PurifyAllHandlesInuse(void) { avoidGy_10++; return 0; } -__declspec(dllexport) int __cdecl PurifyNewHandlesInuse(void) { avoidGy_11++; return 0; } -__declspec(dllexport) int __cdecl PurifyDescribe(void *addr) { avoidGy_12++; addr; return 0; } -__declspec(dllexport) int __cdecl PurifyWhatColors(void *addr, int size) { avoidGy_13++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyAssertIsReadable(const void *addr, int size) { avoidGy_14++; addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyAssertIsWritable(const void *addr, int size) { avoidGy_15++; addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyIsReadable(const void *addr, int size) { avoidGy_16++; addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyIsWritable(const void *addr, int size) { avoidGy_17++; addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyIsInitialized(const void *addr, int size) { avoidGy_18++; addr; size; return 1; } -__declspec(dllexport) int __cdecl PurifyRed(void *addr, int size) { avoidGy_19++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyGreen(void *addr, int size) { avoidGy_20++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyYellow(void *addr, int size) { avoidGy_21++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyBlue(void *addr, int size) { avoidGy_22++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkAsInitialized(void *addr, int size) { avoidGy_23++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkAsUninitialized(void *addr, int size) { avoidGy_24++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkForTrap(void *addr, int size) { avoidGy_25++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyMarkForNoTrap(void *addr, int size) { avoidGy_26++; addr; size; return 0; } -__declspec(dllexport) int __cdecl PurifyHeapValidate(unsigned int hHeap, unsigned int dwFlags, const void *addr) - { avoidGy_27++; hHeap; dwFlags; addr; return 1; } -__declspec(dllexport) int __cdecl PurifySetLateDetectScanCounter(int counter) { avoidGy_28++; counter; return 0; }; -__declspec(dllexport) int __cdecl PurifySetLateDetectScanInterval(int seconds) { avoidGy_29++; seconds; return 0; }; -__declspec(dllexport) int __cdecl CoverageIsRunning(void) { avoidGy_30++; return 0; } -__declspec(dllexport) int __cdecl CoverageDisableRecordingData(void) { avoidGy_31++; return 0; } -__declspec(dllexport) int __cdecl CoverageStartRecordingData(void) { avoidGy_32++; return 0; } -__declspec(dllexport) int __cdecl CoverageStopRecordingData(void) { avoidGy_33++; return 0; } -__declspec(dllexport) int __cdecl CoverageClearData(void) { avoidGy_34++; return 0; } -__declspec(dllexport) int __cdecl CoverageIsRecordingData(void) { avoidGy_35++; return 0; } -__declspec(dllexport) int __cdecl CoverageAddAnnotation(char *str) { avoidGy_36++; str; return 0; } -__declspec(dllexport) int __cdecl CoverageSaveData(void) { avoidGy_37++; return 0; } -__declspec(dllexport) int __cdecl QuantifyIsRunning(void) { avoidGy_42++; return 0; } -__declspec(dllexport) int __cdecl QuantifyDisableRecordingData(void) { avoidGy_43++; return 0; } -__declspec(dllexport) int __cdecl QuantifyStartRecordingData(void) { avoidGy_44++; return 0; } -__declspec(dllexport) int __cdecl QuantifyStopRecordingData(void) { avoidGy_45++; return 0; } -__declspec(dllexport) int __cdecl QuantifyClearData(void) { avoidGy_46++; return 0; } -__declspec(dllexport) int __cdecl QuantifyIsRecordingData(void) { avoidGy_47++; return 0; } -__declspec(dllexport) int __cdecl QuantifyAddAnnotation(char *str) { avoidGy_48++; str; return 0; } -__declspec(dllexport) int __cdecl QuantifySaveData(void) { avoidGy_49++; return 0; } -__declspec(dllexport) int __cdecl PurelockIsRunning(void) { avoidGy_PL_01++; return 0; }