diff --git a/dom/inputmethod/HardwareKeyHandler.cpp b/dom/inputmethod/HardwareKeyHandler.cpp index 8e20fee22e..1d32e7dc0b 100644 --- a/dom/inputmethod/HardwareKeyHandler.cpp +++ b/dom/inputmethod/HardwareKeyHandler.cpp @@ -477,7 +477,7 @@ HardwareKeyHandler::GetCurrentTarget() return nullptr; } - nsCOMPtr focusedWindow; + nsCOMPtr domWindow; fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); if (NS_WARN_IF(!focusedWindow)) { return nullptr; diff --git a/extensions/cookie/test/unit/test_eviction.js b/extensions/cookie/test/unit/test_eviction.js index 5e923d2bc1..5c0cf010fc 100644 --- a/extensions/cookie/test/unit/test_eviction.js +++ b/extensions/cookie/test/unit/test_eviction.js @@ -1,7 +1,9 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -let test_generator = do_run_test(); +"use strict"; + +var test_generator = do_run_test(); function run_test() { @@ -31,10 +33,10 @@ function repeat_test() } // Purge threshold, in seconds. -let gPurgeAge = 1; +var gPurgeAge = 1; // Short expiry age, in seconds. -let gShortExpiry = 2; +var gShortExpiry = 2; // Required delay to ensure a purge occurs, in milliseconds. This must be at // least gPurgeAge + 10%, and includes a little fuzz to account for timer @@ -231,7 +233,7 @@ function get_creationTime(i) function check_remaining_cookies(aNumberTotal, aNumberOld, aNumberToExpect) { var enumerator = Services.cookiemgr.enumerator; - i = 0; + let i = 0; while (enumerator.hasMoreElements()) { var cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); ++i; diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp index a5e93db6fd..18781e3bd9 100644 --- a/image/Downscaler.cpp +++ b/image/Downscaler.cpp @@ -11,6 +11,7 @@ #include "gfxPrefs.h" #include "image_operations.h" #include "mozilla/SSE.h" +#include "mozilla/mips.h" #include "convolver.h" #include "skia/include/core/SkTypes.h" @@ -228,7 +229,7 @@ Downscaler::CommitRow() if (mCurrentInLine == inLineToRead) { skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter, mWindow[mLinesInBuffer++], mHasAlpha, - supports_sse2()); + supports_sse2() || supports_mmi()); } MOZ_ASSERT(mCurrentOutLine < mTargetSize.height, @@ -316,7 +317,7 @@ Downscaler::DownscaleInputLine() &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)]; skia::ConvolveVertically(static_cast(filterValues), filterLength, mWindow.get(), mXFilter->num_values(), - outputLine, mHasAlpha, supports_sse2()); + outputLine, mHasAlpha, supports_sse2() || supports_mmi()); mCurrentOutLine += 1; diff --git a/image/DownscalingFilter.h b/image/DownscalingFilter.h index 564c577be3..c17d24ccb8 100644 --- a/image/DownscalingFilter.h +++ b/image/DownscalingFilter.h @@ -23,6 +23,7 @@ #include "mozilla/Maybe.h" #include "mozilla/SSE.h" +#include "mozilla/mips.h" #include "mozilla/UniquePtr.h" #include "mozilla/gfx/2D.h" #include "gfxPrefs.h" @@ -236,7 +237,7 @@ public: if (mInputRow == inputRowToRead) { skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter, mWindow[mRowsInWindow++], mHasAlpha, - supports_sse2()); + supports_sse2() || supports_mmi()); } MOZ_ASSERT(mOutputRow < mNext.InputSize().height, @@ -311,7 +312,7 @@ private: skia::ConvolveVertically(static_cast(filterValues), filterLength, mWindow.get(), mXFilter->num_values(), reinterpret_cast(aRow), mHasAlpha, - supports_sse2()); + supports_sse2() || supports_mmi()); return Some(WriteState::NEED_MORE_DATA); }); diff --git a/image/DrawResult.h b/image/DrawResult.h index 307255d1ab..dd7fdec1ef 100644 --- a/image/DrawResult.h +++ b/image/DrawResult.h @@ -45,7 +45,7 @@ namespace image { * * BAD_ARGS: We failed to draw because bad arguments were passed to draw(). */ -enum class DrawResult : uint8_t +enum class MOZ_MUST_USE DrawResult : uint8_t { SUCCESS, INCOMPLETE, diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp index 48d598098e..d38796134d 100644 --- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -372,22 +372,17 @@ nsContainerFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset, ///////////////////////////////////////////////////////////////////////////// // Helper member functions -static nsresult +static void ReparentFrameViewTo(nsIFrame* aFrame, nsViewManager* aViewManager, nsView* aNewParentView, nsView* aOldParentView) { - - // XXX What to do about placeholder views for "position: fixed" elements? - // They should be reparented too. - - // Does aFrame have a view? if (aFrame->HasView()) { #ifdef MOZ_XUL if (aFrame->GetType() == nsGkAtoms::menuPopupFrame) { // This view must be parented by the root view, don't reparent it. - return NS_OK; + return; } #endif nsView* view = aFrame->GetView(); @@ -400,7 +395,7 @@ ReparentFrameViewTo(nsIFrame* aFrame, // The view will remember the Z-order and other attributes that have been set on it. nsView* insertBefore = nsLayoutUtils::FindSiblingViewFor(aNewParentView, aFrame); aViewManager->InsertChild(aNewParentView, view, insertBefore, insertBefore != nullptr); - } else { + } else if (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW) { nsIFrame::ChildListIterator lists(aFrame); for (; !lists.IsDone(); lists.Next()) { // Iterate the child frames, and check each child frame to see if it has @@ -412,8 +407,6 @@ ReparentFrameViewTo(nsIFrame* aFrame, } } } - - return NS_OK; } void @@ -545,8 +538,8 @@ nsContainerFrame::ReparentFrameView(nsIFrame* aChildFrame, // anything if (oldParentView != newParentView) { // They're not so we need to reparent any child views - return ReparentFrameViewTo(aChildFrame, oldParentView->GetViewManager(), newParentView, - oldParentView); + ReparentFrameViewTo(aChildFrame, oldParentView->GetViewManager(), newParentView, + oldParentView); } return NS_OK; diff --git a/layout/generic/nsContainerFrame.h b/layout/generic/nsContainerFrame.h index a3d25fe18c..9898b2418a 100644 --- a/layout/generic/nsContainerFrame.h +++ b/layout/generic/nsContainerFrame.h @@ -34,7 +34,10 @@ class FramePropertyTable; // dependency on nsDeviceContext.h. It doesn't matter if it's a // little off. #ifdef DEBUG -#define CRAZY_COORD (1000000*60) +// 10 million pixels, converted to app units. Note that this a bit larger +// than 1/4 of nscoord_MAX. So, if any content gets to be this large, we're +// definitely in danger of grazing up against nscoord_MAX; hence, it's CRAZY. +#define CRAZY_COORD (10000000*60) #define CRAZY_SIZE(_x) (((_x) < -CRAZY_COORD) || ((_x) > CRAZY_COORD)) #endif diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 4b3d356d05..a78b6385f2 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -910,12 +910,10 @@ KTableEntry nsCSSProps::kBackgroundClipKTable[] = { { eCSSKeyword_UNKNOWN, -1 } }; -#if 0 -static_assert(ArrayLength(nsCSSProps::kImageLayerOriginKTable) == - ArrayLength(nsCSSProps::kBackgroundClipKTable) - 1, +static_assert(MOZ_ARRAY_LENGTH(nsCSSProps::kImageLayerOriginKTable) == + MOZ_ARRAY_LENGTH(nsCSSProps::kBackgroundClipKTable) - 1, "background-clip has one extra value, which is text, compared" "to {background,mask}-origin"); -#endif // Note: Don't change this table unless you update // ParseImageLayerPosition! diff --git a/memory/mozalloc/throw_gcc.h b/memory/mozalloc/throw_gcc.h index db81d4f936..cb3da24c2e 100644 --- a/memory/mozalloc/throw_gcc.h +++ b/memory/mozalloc/throw_gcc.h @@ -8,6 +8,8 @@ #ifndef mozilla_throw_gcc_h #define mozilla_throw_gcc_h +#if !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION < 14000 + #include "mozilla/Attributes.h" #include // snprintf @@ -151,4 +153,9 @@ __throw_system_error(int err) } // namespace std +#undef MOZ_THROW_NORETURN +#undef MOZ_THROW_INLINE + +#endif + #endif // mozilla_throw_gcc_h diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 26b2213d01..6bfe0e4381 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2217,6 +2217,9 @@ pref("network.stricttransportsecurity.enabled", true); // Use the HSTS preload list by default pref("network.stricttransportsecurity.preloadlist", true); +// Use JS mDNS as a fallback +pref("network.mdns.use_js_fallback", false); + pref("converter.html2txt.structs", true); // Output structured phrases (strong, em, code, sub, sup, b, i, u) pref("converter.html2txt.header_strategy", 1); // 0 = no indention; 1 = indention, increased with header level; 2 = numbering and slight indention // Whether we include ruby annotation in the text despite whether it diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp index 98dbff3a4a..706352d569 100644 --- a/netwerk/cache2/CacheFileIOManager.cpp +++ b/netwerk/cache2/CacheFileIOManager.cpp @@ -532,9 +532,9 @@ CacheFileHandles::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const class ShutdownEvent : public nsRunnable { public: - ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar) - : mLock(aLock) - , mCondVar(aCondVar) + ShutdownEvent() + : mMonitor("ShutdownEvent.mMonitor") + , mNotified(false) , mPrepare(true) { MOZ_COUNT_CTOR(ShutdownEvent); @@ -564,18 +564,35 @@ public: return NS_OK; } - MutexAutoLock lock(*mLock); + MonitorAutoLock mon(mMonitor); CacheFileIOManager::gInstance->ShutdownInternal(); - mCondVar->Notify(); + mNotified = true; + mon.Notify(); + return NS_OK; } + void PostAndWait() + { + MonitorAutoLock mon(mMonitor); + + DebugOnly rv; + nsCOMPtr ioTarget = + CacheFileIOManager::gInstance->mIOThread->Target(); + MOZ_ASSERT(ioTarget); + rv = ioTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + while (!mNotified) { + mon.Wait(); + } + } + protected: - mozilla::Mutex *mLock; - mozilla::CondVar *mCondVar; - bool mPrepare; + mozilla::Monitor mMonitor; + bool mNotified; + bool mPrepare; }; class OpenFileEvent : public nsRunnable { @@ -1157,23 +1174,14 @@ CacheFileIOManager::Shutdown() return NS_ERROR_NOT_INITIALIZED; } + gInstance->mShutdownDemanded = true; + CacheIndex::PreShutdown(); ShutdownMetadataWriteScheduling(); - { - mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock"); - mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar"); - - MutexAutoLock autoLock(lock); - RefPtr ev = new ShutdownEvent(&lock, &condVar); - DebugOnly rv; - nsCOMPtr ioTarget = gInstance->mIOThread->Target(); - MOZ_ASSERT(ioTarget); - rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - condVar.Wait(); - } + RefPtr ev = new ShutdownEvent(); + ev->PostAndWait(); MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0); MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0); @@ -2289,15 +2297,21 @@ CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle, found = mHandlesByLastUsed.RemoveElement(aHandle); MOZ_ASSERT(found); - if (aIgnoreShutdownLag || !IsPastShutdownIOLag()) { - PR_Close(aHandle->mFD); - } else { + // Leak invalid (w/o metadata) and doomed handles immediately after shutdown. + // Leak other handles when past the shutdown time maximum lag. + if ( +#ifndef DEBUG + ((aHandle->mInvalid || aHandle->mIsDoomed) && MOZ_UNLIKELY(mShutdownDemanded)) || +#endif + MOZ_UNLIKELY(!aIgnoreShutdownLag && IsPastShutdownIOLag())) { // Pretend this file has been validated (the metadata has been written) // to prevent removal I/O on this apparently used file. The entry will // never be used, since it doesn't have correct metadata, thus we don't // need to worry about removing it. aHandle->mInvalid = false; LOG((" past the shutdown I/O lag, leaking file handle")); + } else { + PR_Close(aHandle->mFD); } aHandle->mFD = nullptr; @@ -4033,13 +4047,16 @@ public: } mozilla::MonitorAutoLock mon(mMonitor); + mMonitorNotified = false; nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); if (NS_FAILED(rv)) { NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles"); return 0; } - mon.Wait(); + while (!mMonitorNotified) { + mon.Wait(); + } return mSize; } @@ -4053,12 +4070,14 @@ public: mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf); } + mMonitorNotified = true; mon.Notify(); return NS_OK; } private: mozilla::Monitor mMonitor; + bool mMonitorNotified; mozilla::MallocSizeOf mMallocSizeOf; CacheFileHandles const &mHandles; nsTArray const &mSpecialHandles; diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h index 922faf0286..ed2c3e933a 100644 --- a/netwerk/cache2/CacheFileIOManager.h +++ b/netwerk/cache2/CacheFileIOManager.h @@ -444,6 +444,10 @@ private: // Shutdown time stamp, accessed only on the I/O thread. Used to bypass // I/O after a certain time pass the shutdown has been demanded. TimeStamp mShutdownDemandedTime; + // Set true on the main thread when cache shutdown is first demanded. + Atomic mShutdownDemanded; + // Set true on the IO thread, CLOSE level as part of the internal shutdown + // procedure. bool mShuttingDown; RefPtr mIOThread; nsCOMPtr mCacheDirectory; diff --git a/netwerk/cache2/CacheIOThread.cpp b/netwerk/cache2/CacheIOThread.cpp index 4f5eef27d4..b724a82973 100644 --- a/netwerk/cache2/CacheIOThread.cpp +++ b/netwerk/cache2/CacheIOThread.cpp @@ -157,8 +157,9 @@ already_AddRefed CacheIOThread::Target() if (!target && mThread) { MonitorAutoLock lock(mMonitor); - if (!mXPCOMThread) + while (!mXPCOMThread) { lock.Wait(); + } target = mXPCOMThread; } diff --git a/netwerk/cache2/CacheIOThread.h b/netwerk/cache2/CacheIOThread.h index 553bd5e1f2..314378f222 100644 --- a/netwerk/cache2/CacheIOThread.h +++ b/netwerk/cache2/CacheIOThread.h @@ -36,7 +36,7 @@ public: READ, MANAGEMENT, WRITE, - CLOSE, + CLOSE = WRITE, INDEX, EVICT, LAST_LEVEL, diff --git a/netwerk/cache2/CacheObserver.cpp b/netwerk/cache2/CacheObserver.cpp index 5c37425bb7..6e452cec2f 100644 --- a/netwerk/cache2/CacheObserver.cpp +++ b/netwerk/cache2/CacheObserver.cpp @@ -13,6 +13,7 @@ #include "mozilla/Preferences.h" #include "mozilla/TimeStamp.h" #include "nsServiceManagerUtils.h" +#include "mozilla/net/NeckoCommon.h" #include "prsystem.h" #include #include @@ -98,6 +99,10 @@ NS_IMPL_ISUPPORTS(CacheObserver, nsresult CacheObserver::Init() { + if (IsNeckoChild()) { + return NS_OK; + } + if (sSelf) { return NS_OK; } @@ -446,18 +451,13 @@ CacheObserver::Observe(nsISupports* aSubject, return NS_OK; } - if (!strcmp(aTopic, "profile-before-change")) { + if (!strcmp(aTopic, "profile-change-net-teardown") || + !strcmp(aTopic, "profile-before-change") || + !strcmp(aTopic, "xpcom-shutdown")) { RefPtr service = CacheStorageService::Self(); - if (service) - service->Shutdown(); - - return NS_OK; - } - - if (!strcmp(aTopic, "xpcom-shutdown")) { - RefPtr service = CacheStorageService::Self(); - if (service) + if (service) { service->Shutdown(); + } CacheFileIOManager::Shutdown(); return NS_OK; @@ -465,8 +465,9 @@ CacheObserver::Observe(nsISupports* aSubject, if (!strcmp(aTopic, "last-pb-context-exited")) { RefPtr service = CacheStorageService::Self(); - if (service) + if (service) { service->DropPrivateBrowsingEntries(); + } return NS_OK; } diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp index 76558942b2..3ec729df8f 100644 --- a/netwerk/cache2/CacheStorageService.cpp +++ b/netwerk/cache2/CacheStorageService.cpp @@ -2084,7 +2084,9 @@ NS_IMETHODIMP CacheStorageService::IOThreadSuspender::Run() { MonitorAutoLock mon(mMon); - mon.Wait(); + while (!mSignaled) { + mon.Wait(); + } return NS_OK; } @@ -2092,6 +2094,7 @@ void CacheStorageService::IOThreadSuspender::Notify() { MonitorAutoLock mon(mMon); + mSignaled = true; mon.Notify(); } diff --git a/netwerk/cache2/CacheStorageService.h b/netwerk/cache2/CacheStorageService.h index e777d8665e..229fb57f0f 100644 --- a/netwerk/cache2/CacheStorageService.h +++ b/netwerk/cache2/CacheStorageService.h @@ -366,13 +366,14 @@ private: class IOThreadSuspender : public nsRunnable { public: - IOThreadSuspender() : mMon("IOThreadSuspender") { } + IOThreadSuspender() : mMon("IOThreadSuspender"), mSignaled(false) { } void Notify(); private: virtual ~IOThreadSuspender() { } NS_IMETHOD Run() override; Monitor mMon; + bool mSignaled; }; RefPtr mActiveIOSuspender; diff --git a/netwerk/cookie/nsCookie.h b/netwerk/cookie/nsCookie.h index 3c06df5d6a..58154b0fa0 100644 --- a/netwerk/cookie/nsCookie.h +++ b/netwerk/cookie/nsCookie.h @@ -56,9 +56,9 @@ class nsCookie : public nsICookie2 , mExpiry(aExpiry) , mLastAccessed(aLastAccessed) , mCreationTime(aCreationTime) - , mIsSession(aIsSession != false) - , mIsSecure(aIsSecure != false) - , mIsHttpOnly(aIsHttpOnly != false) + , mIsSession(aIsSession) + , mIsSecure(aIsSecure) + , mIsHttpOnly(aIsHttpOnly) , mOriginAttributes(aOriginAttributes) { } @@ -101,7 +101,7 @@ class nsCookie : public nsICookie2 // setters inline void SetExpiry(int64_t aExpiry) { mExpiry = aExpiry; } inline void SetLastAccessed(int64_t aTime) { mLastAccessed = aTime; } - inline void SetIsSession(bool aIsSession) { mIsSession = (bool) aIsSession; } + inline void SetIsSession(bool aIsSession) { mIsSession = aIsSession; } // Set the creation time manually, overriding the monotonicity checks in // Create(). Use with caution! inline void SetCreationTime(int64_t aTime) { mCreationTime = aTime; } @@ -111,6 +111,7 @@ class nsCookie : public nsICookie2 protected: virtual ~nsCookie() {} + private: // member variables // we use char* ptrs to store the strings in a contiguous block, // so we save on the overhead of using nsCStrings. However, we diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp index c3cd589282..9b8d640b8e 100644 --- a/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp +++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp @@ -281,7 +281,11 @@ GetAddrInfoReplyRunnable::Reply(DNSServiceRef aSdRef, } NetAddr address; - memcpy(&address, aAddress, sizeof(*aAddress)); + address.raw.family = aAddress->sa_family; + + static_assert(sizeof(address.raw.data) >= sizeof(aAddress->sa_data), + "size of sockaddr.sa_data is too big"); + memcpy(&address.raw.data, aAddress->sa_data, sizeof(aAddress->sa_data)); thread->Dispatch(new GetAddrInfoReplyRunnable(aSdRef, aFlags, diff --git a/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm new file mode 100644 index 0000000000..771f9a794d --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm @@ -0,0 +1,244 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["MulticastDNS"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Messaging.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +var log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "MulticastDNS"); + +const FAILURE_INTERNAL_ERROR = -65537; + +// Helper function for sending commands to Java. +function send(type, data, callback) { + let msg = { + type: type + }; + + for (let i in data) { + try { + msg[i] = data[i]; + } catch (e) { + } + } + + Messaging.sendRequestForResult(msg) + .then(result => callback(result, null), + err => callback(null, typeof err === "number" ? err : FAILURE_INTERNAL_ERROR)); +} + +// Receives service found/lost event from NsdManager +function ServiceManager() { +} + +ServiceManager.prototype = { + listeners: {}, + numListeners: 0, + + registerEvent: function() { + log("registerEvent"); + Messaging.addListener(this.onServiceFound.bind(this), "NsdManager:ServiceFound"); + Messaging.addListener(this.onServiceLost.bind(this), "NsdManager:ServiceLost"); + }, + + unregisterEvent: function() { + log("unregisterEvent"); + Messaging.removeListener("NsdManager:ServiceFound"); + Messaging.removeListener("NsdManager:ServiceLost"); + }, + + addListener: function(aServiceType, aListener) { + log("addListener: " + aServiceType + ", " + aListener); + + if (!this.listeners[aServiceType]) { + this.listeners[aServiceType] = []; + } + if (this.listeners[aServiceType].includes(aListener)) { + log("listener already exists"); + return; + } + + this.listeners[aServiceType].push(aListener); + ++this.numListeners; + + if (this.numListeners === 1) { + this.registerEvent(); + } + + log("listener added: " + this); + }, + + removeListener: function(aServiceType, aListener) { + log("removeListener: " + aServiceType + ", " + aListener); + + if (!this.listeners[aServiceType]) { + log("listener doesn't exist"); + return; + } + let index = this.listeners[aServiceType].indexOf(aListener); + if (index < 0) { + log("listener doesn't exist"); + return; + } + + this.listeners[aServiceType].splice(index, 1); + --this.numListeners; + + if (this.numListeners === 0) { + this.unregisterEvent(); + } + + log("listener removed" + this); + }, + + onServiceFound: function(aServiceInfo) { + let listeners = this.listeners[aServiceInfo.serviceType]; + if (listeners) { + for (let listener of listeners) { + listener.onServiceFound(aServiceInfo); + } + } else { + log("no listener"); + } + return {}; + }, + + onServiceLost: function(aServiceInfo) { + let listeners = this.listeners[aServiceInfo.serviceType]; + if (listeners) { + for (let listener of listeners) { + listener.onServiceLost(aServiceInfo); + } + } else { + log("no listener"); + } + return {}; + } +}; + +// make an object from nsIPropertyBag2 +function parsePropertyBag2(bag) { + if (!bag || !(bag instanceof Ci.nsIPropertyBag2)) { + throw new TypeError("Not a property bag"); + } + + let attributes = []; + let enumerator = bag.enumerator; + while (enumerator.hasMoreElements()) { + let name = enumerator.getNext().QueryInterface(Ci.nsIProperty).name; + let value = bag.getPropertyAsACString(name); + attributes.push({ + "name": name, + "value": value + }); + } + + return attributes; +} + +function MulticastDNS() { + this.serviceManager = new ServiceManager(); +} + +MulticastDNS.prototype = { + startDiscovery: function(aServiceType, aListener) { + this.serviceManager.addListener(aServiceType, aListener); + + let serviceInfo = { + serviceType: aServiceType, + uniqueId: aListener.uuid + }; + + send("NsdManager:DiscoverServices", serviceInfo, (result, err) => { + if (err) { + log("onStartDiscoveryFailed: " + aServiceType + " (" + err + ")"); + this.serviceManager.removeListener(aServiceType, aListener); + aListener.onStartDiscoveryFailed(aServiceType, err); + } else { + aListener.onDiscoveryStarted(result); + } + }); + }, + + stopDiscovery: function(aServiceType, aListener) { + this.serviceManager.removeListener(aServiceType, aListener); + + let serviceInfo = { + uniqueId: aListener.uuid + }; + + send("NsdManager:StopServiceDiscovery", serviceInfo, (result, err) => { + if (err) { + log("onStopDiscoveryFailed: " + aServiceType + " (" + err + ")"); + aListener.onStopDiscoveryFailed(aServiceType, err); + } else { + aListener.onDiscoveryStopped(aServiceType); + } + }); + }, + + registerService: function(aServiceInfo, aListener) { + let serviceInfo = { + port: aServiceInfo.port, + serviceType: aServiceInfo.serviceType, + uniqueId: aListener.uuid + }; + + try { + serviceInfo.host = aServiceInfo.host; + } catch(e) { + // host unspecified + } + try { + serviceInfo.serviceName = aServiceInfo.serviceName; + } catch(e) { + // serviceName unspecified + } + try { + serviceInfo.attributes = parsePropertyBag2(aServiceInfo.attributes); + } catch(e) { + // attributes unspecified + } + + send("NsdManager:RegisterService", serviceInfo, (result, err) => { + if (err) { + log("onRegistrationFailed: (" + err + ")"); + aListener.onRegistrationFailed(aServiceInfo, err); + } else { + aListener.onServiceRegistered(result); + } + }); + }, + + unregisterService: function(aServiceInfo, aListener) { + let serviceInfo = { + uniqueId: aListener.uuid + }; + + send("NsdManager:UnregisterService", serviceInfo, (result, err) => { + if (err) { + log("onUnregistrationFailed: (" + err + ")"); + aListener.onUnregistrationFailed(aServiceInfo, err); + } else { + aListener.onServiceUnregistered(aServiceInfo); + } + }); + }, + + resolveService: function(aServiceInfo, aListener) { + send("NsdManager:ResolveService", aServiceInfo, (result, err) => { + if (err) { + log("onResolveFailed: (" + err + ")"); + aListener.onResolveFailed(aServiceInfo, err); + } else { + aListener.onServiceResolved(result); + } + }); + } +}; diff --git a/netwerk/dns/mdns/libmdns/MulticastDNSFallback.jsm b/netwerk/dns/mdns/libmdns/MulticastDNSFallback.jsm new file mode 100644 index 0000000000..9b3f3c2487 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MulticastDNSFallback.jsm @@ -0,0 +1,577 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['MulticastDNS']; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +const MDNS_PORT = 5353; +const MDNS_ADDRESS = '224.0.0.251'; + +const DNS_REC_TYPE_PTR = 12; +const DNS_REC_TYPE_TXT = 16; +const DNS_REC_TYPE_SRV = 33; +const DNS_REC_TYPE_A = 1; +const DNS_REC_TYPE_NSEC= 47; + +const DNS_CLASS_QU = 0x8000; +const DNS_CLASS_IN = 0x0001; + +const DNS_SECTION_QD = 'qd'; +const DNS_SECTION_AN = 'an'; +const DNS_SECTION_NS = 'ns'; +const DNS_SECTION_AR = 'ar'; + +const DEBUG = false; + +function debug(msg) { + Services.console.logStringMessage('MulticastDNSFallback: ' + msg); +} + +/* The following was taken from https://raw.githubusercontent.com/GoogleChrome/chrome-app-samples/master/mdns-browser/dns.js */ + +/** + * DataWriter writes data to an ArrayBuffer, presenting it as the instance + * variable 'buffer'. + * + * @constructor + */ +let DataWriter = function(opt_size) { + let loc = 0; + let view = new Uint8Array(new ArrayBuffer(opt_size || 512)); + + this.byte_ = function(v) { + view[loc] = v; + ++loc; + this.buffer = view.buffer.slice(0, loc); + }.bind(this); +}; + +DataWriter.prototype.byte = function(v) { + this.byte_(v); + return this; +}; + +DataWriter.prototype.short = function(v) { + return this.byte((v >> 8) & 0xff).byte(v & 0xff); +}; + +DataWriter.prototype.long = function(v) { + return this.short((v >> 16) & 0xffff).short(v & 0xffff); +}; + +/** + * Writes a DNS name. If opt_ref is specified, will finish this name with a + * suffix reference (i.e., 0xc0 ). If not, then will terminate with a NULL + * byte. + */ +DataWriter.prototype.name = function(v, opt_ref) { + let parts = v.split('.'); + parts.forEach(function(part) { + this.byte(part.length); + for (let i = 0; i < part.length; ++i) { + this.byte(part.charCodeAt(i)); + } + }.bind(this)); + if (opt_ref) { + this.byte(0xc0).byte(opt_ref); + } else { + this.byte(0); + } + return this; +}; + +/** + * DataConsumer consumes data from an ArrayBuffer. + * + * @constructor + */ +let DataConsumer = function(arg) { + if (arg instanceof Uint8Array) { + this.view_ = arg; + } else { + this.view_ = new Uint8Array(arg); + } + this.loc_ = 0; +}; + +/** + * @return whether this DataConsumer has consumed all its data + */ +DataConsumer.prototype.isEOF = function() { + return this.loc_ >= this.view_.byteLength; +}; + +/** + * @param length {integer} number of bytes to return from the front of the view + * @return a Uint8Array + */ +DataConsumer.prototype.slice = function(length) { + let view = this.view_.subarray(this.loc_, this.loc_ + length); + this.loc_ += length; + return view; +}; + +DataConsumer.prototype.byte = function() { + this.loc_ += 1; + return this.view_[this.loc_ - 1]; +}; + +DataConsumer.prototype.short = function() { + return (this.byte() << 8) + this.byte(); +}; + +DataConsumer.prototype.long = function() { + return (this.short() << 16) + this.short(); +}; + +/** + * Consumes a DNS name, which will finish with a NULL byte. + */ +DataConsumer.prototype.name = function() { + let parts = []; + for (;;) { + let len = this.byte(); + if (!len) { + break; + } else if (len == 0xc0) { + // concat suffix reference (i.e., 0xc0 ). + let ref = this.byte(); + let refConsumer = new DataConsumer(new Uint8Array(this.view_.buffer, ref)); + parts.push(refConsumer.name()); + break; + } + + // Otherwise, consume a string! + let v = ''; + while (len-- > 0) { + v += String.fromCharCode(this.byte()); + } + parts.push(v); + } + return parts.join('.'); +}; + +/** + * Consumes a string according to length. + */ +DataConsumer.prototype.string = function() { + let len = this.byte(); + if (!len) { + return; + } + + let v = ''; + while (len-- > 0) { + v += String.fromCharCode(this.byte()); + } + return v; +}; + +/** + * DNSPacket holds the state of a DNS packet. It can be modified or serialized + * in-place. + * + * @constructor + */ +let DNSPacket = function(opt_flags) { + this.flags_ = opt_flags || 0; /* uint16 */ + this.data_ = {}; + this.data_[DNS_SECTION_QD] = []; + this.data_[DNS_SECTION_AN] = []; + this.data_[DNS_SECTION_NS] = []; + this.data_[DNS_SECTION_AR] = []; +}; + +/** + * Parse a DNSPacket from an ArrayBuffer (or Uint8Array). + */ +DNSPacket.parse = function(buffer) { + let consumer = new DataConsumer(buffer); + if (consumer.short()) { + throw new Error('DNS packet must start with 00 00'); + } + let flags = consumer.short(); + let count = {}; + count[DNS_SECTION_QD] = consumer.short(); + count[DNS_SECTION_AN] = consumer.short(); + count[DNS_SECTION_NS] = consumer.short(); + count[DNS_SECTION_AR] = consumer.short(); + + let packet = new DNSPacket(flags); + + // Parse the QUESTION section. + for (let i = 0; i < count[DNS_SECTION_QD]; ++i) { + let part = new DNSRecord( + consumer.name(), + consumer.short(), // type + consumer.short()); // class + packet.push(DNS_SECTION_QD, part); + } + + // Parse the ANSWER, AUTHORITY and ADDITIONAL sections. + [DNS_SECTION_AN, DNS_SECTION_NS, DNS_SECTION_AR].forEach(function(section) { + for (let i = 0; i < count[section]; ++i) { + let part = new DNSRecord( + consumer.name(), + consumer.short(), + consumer.short(), // class + consumer.long(), // ttl + consumer.slice(consumer.short())); + packet.push(section, part); + } + }); + + if (consumer.isEOF()) { + DEBUG && debug('was not EOF on incoming packet'); + } + return packet; +}; + +DNSPacket.prototype.push = function(section, record) { + this.data_[section].push(record); +}; + +DNSPacket.prototype.each = function(section) { + let filter = false; + let call; + if (arguments.length == 2) { + call = arguments[1]; + } else { + filter = arguments[1]; + call = arguments[2]; + } + this.data_[section].forEach(function(rec) { + if (!filter || rec.type == filter) { + call(rec); + } + }); +}; + +/** + * Serialize this DNSPacket into an ArrayBuffer for sending over UDP. + */ +DNSPacket.prototype.serialize = function() { + let out = new DataWriter(); + let s = [DNS_SECTION_QD, DNS_SECTION_AN, DNS_SECTION_NS, DNS_SECTION_AR]; + + out.short(0).short(this.flags_); + + s.forEach(function(section) { + out.short(this.data_[section].length); + }.bind(this)); + + s.forEach(function(section) { + this.data_[section].forEach(function(rec) { + out.name(rec.name).short(rec.type).short(rec.cl); + + if (section != DNS_SECTION_QD) { + // TODO: implement .bytes() + throw new Error('can\'t yet serialize non-QD records'); + // out.long(rec.ttl).bytes(rec.data_); + } + }); + }.bind(this)); + + return out.buffer; +}; + +/** + * DNSRecord is a record inside a DNS packet; e.g. a QUESTION, or an ANSWER, + * AUTHORITY, or ADDITIONAL record. Note that QUESTION records are special, + * and do not have ttl or data. + */ +let DNSRecord = function(name, type, cl, opt_ttl, opt_data) { + this.name = name; + this.type = type; + this.cl = cl; + + this.isQD = (arguments.length == 3); + if (!this.isQD) { + this.ttl = opt_ttl; + this.data_ = opt_data; + } +}; + +DNSRecord.prototype.asName = function() { + return new DataConsumer(this.data_).name(); +}; + +DNSRecord.prototype.asSRV = function() { + if (this.type !== DNS_REC_TYPE_SRV) { + return null; + } + + let consumer = new DataConsumer(this.data_); + let data_length = this.data_.length; + return { + priority: consumer.short(), + weight: consumer.short(), + port: consumer.short(), + target: consumer.name() + consumer.name(), + }; +}; + +DNSRecord.prototype.asTXT = function() { + if (this.type !== DNS_REC_TYPE_TXT) { + return null; + } + + let consumer = new DataConsumer(this.data_); + let attributes = []; + while(!consumer.isEOF()) { + attributes.push(consumer.string()); + } + + return attributes; +}; + +/* end https://raw.githubusercontent.com/GoogleChrome/chrome-app-samples/master/mdns-browser/dns.js */ + +/** + * Parse fully qualified domain name to service name, instance name, + * and domain name. See https://tools.ietf.org/html/rfc6763#section-7. + * + * example: The Server._http._tcp.example.com + * instance name = "The Server" + * service type = "_http._tcp" + * domain = "example.com" + * @private + */ +function _parseDomainName(str) { + let items = str.split('.'); + let idx = items.findIndex(function(element) { + return element === '_tcp' || element === '_udp'; + }); + + return { + instanceName: items.splice(0, idx - 1).join('.'), + serviceType: items.splice(0, 2). join('.'), + domainName: items.join('.') + }; +} + +function _createPropertyBag(map) { + let bag = Cc['@mozilla.org/hash-property-bag;1'] + .createInstance(Ci.nsIWritablePropertyBag); + + for (let entry of map.entries()) { + bag.setProperty(entry[0], entry[1]); + } + + return bag; +} + +let MulticastDNS = function() { + this._targets = new Map(); +}; + +MulticastDNS.prototype = { + socket: null, + //public API + startDiscovery: function(aServiceType, aListener) { + DEBUG && debug('startDiscovery for ' + aServiceType); + let { serviceType } = _parseDomainName(aServiceType); + this._addServiceListener(serviceType, aListener); + + try { + this._ensureSocket(); + this._query(serviceType + '.local', DNS_REC_TYPE_PTR); + aListener.onDiscoveryStarted(serviceType); + } catch (e) { + DEBUG && debug('onStartDiscoveryFailed: ' + serviceType + ' (' + e + ')'); + this._removeServiceListener(serviceType, aListener); + aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE); + } + }, + + stopDiscovery: function(aServiceType, aListener) { + DEBUG && debug('stopDiscovery for ' + aServiceType); + let { serviceType } = _parseDomainName(aServiceType); + this._removeServiceListener(serviceType, aListener); + + aListener.onDiscoveryStopped(serviceType); + if (this._targets.size === 0) { + DEBUG && debug('close current socket'); + this.socket.close(); + delete this.socket; + } + }, + + registerService: function(aServiceInfo, aListener) { + DEBUG && debug('service registration is not supported'); + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + unregisterService: function(aServiceInfo, aListener) { + DEBUG && debug('service registration is not supported'); + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + resolveService: function(aServiceInfo, aListener) { + DEBUG && debug('address info is already resolve during discovery phase'); + aListener.onServiceResolved(aServiceInfo); + }, + + //private API + onReceive: function(info) { + let packet = DNSPacket.parse(info.rawData); + let serviceRecords = {}; + + packet.each(DNS_SECTION_AR, DNS_REC_TYPE_SRV, (rec) => { + DEBUG && debug('recieve SRV: ' + rec.name); + let srv = rec.asSRV(); + serviceRecords[rec.name] = { + port: srv.port, + host: srv.target, + }; + }); + + packet.each(DNS_SECTION_AR, DNS_REC_TYPE_TXT, (rec) => { + DEBUG && debug('recieve TXT: ' + rec.name); + if (!serviceRecords[rec.name]) { + return; + } + + let txt = rec.asTXT(); + let attributes = new Map(); + for(let x in txt) { + let idx = x.indexOf('='); + if (idx < 0) { + attributes.set(txt[x], true); + continue; + } + + let key = txt[x].substring(0, idx); + let value = txt[x].substring(idx + 1); + attributes.set(key, value); + } + serviceRecords[rec.name].attributes = attributes; + }); + + packet.each(DNS_SECTION_AN, DNS_REC_TYPE_PTR, (rec) => { + DEBUG && debug('recieve PTR: ' + rec.name); + let { serviceType: answerType } = _parseDomainName(rec.name); + if (this._targets.has(answerType)) { + let name = rec.asName(); + DEBUG && debug('>> for ' + name); + let {instanceName, serviceType, domainName} = _parseDomainName(name); + let serviceInfo = { + host: serviceRecords[name].host, + address: info.fromAddr.address, + port: serviceRecords[name].port, + serviceType: serviceType, + serviceName: instanceName, + domainName: domainName, + attributes: _createPropertyBag(serviceRecords[name].attributes), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceInfo]), + }; + this._targets.get(serviceType).forEach(function(listener) { + listener.onServiceFound(serviceInfo); + }); + } + }); + }, + + /** + * Handles network error occured while waiting for data. + * @private + */ + onReceiveError: function(socket, status) { + DEBUG && debug('receiver socket error' + status); + return true; + }, + + /** + * Broadcasts for services on the given socket/address. + * @private + */ + _query: function(search, type) { + DEBUG && debug('query for service: ' + search); + let packet = new DNSPacket(); + packet.push(DNS_SECTION_QD, new DNSRecord(search, type, DNS_CLASS_IN | DNS_CLASS_QU)); + + this._broadcast(this.socket, packet); + }, + + /** + * Broadcasts a MDNS packet on the given socket/address. + * @private + */ + _broadcast: function(sock, packet) { + let raw = new DataView(packet.serialize()); + let length = raw.byteLength; + let buf = []; + for (let x = 0; x < length; x++) { + let charcode = raw.getUint8(x); + buf[x] = charcode; + } + sock.send(MDNS_ADDRESS, MDNS_PORT, buf, buf.length); + }, + + _addServiceListener: function(serviceType, listener) { + let listeners = this._targets.get(serviceType); + if (!listeners) { + listeners = []; + this._targets.set(serviceType, listeners); + } + + if (!listeners.find((element) => { + return element === listener; + })) { + DEBUG && debug('insert new listener'); + listeners.push(listener); + } + }, + + _removeServiceListener: function(serviceType, listener) { + if (!this._targets.has(serviceType)) { + DEBUG && debug('listener doesnt exist'); + return; + } + + let listeners = this._targets.get(serviceType); + let idx = listeners.findIndex(function(element) { + return element === listener; + }); + + if (idx >= 0) { + listeners.splice(idx, 1); + } + + if (listeners.length === 0) { + this._targets.delete(serviceType); + } + }, + + _ensureSocket: function() { + if (this.socket) { + DEBUG && debug('reuse current socket'); + return; + } + + this.socket = Cc['@mozilla.org/network/udp-socket;1'] + .createInstance(Ci.nsIUDPSocket); + let self = this; + this.socket.init(MDNS_PORT, false, + Services.scriptSecurityManager.getSystemPrincipal()); + this.socket.asyncListen({ + onPacketReceived: function(aSocket, aMessage) { + self.onReceive(aMessage); + }, + + onStopListening: function(aSocket, aStatus) { + self.onReceiveError(aSocket, aStatus); + }, + }); + this.socket.joinMulticast(MDNS_ADDRESS); + }, +}; diff --git a/netwerk/dns/mdns/libmdns/moz.build b/netwerk/dns/mdns/libmdns/moz.build index 6671b5c177..8318ba74df 100644 --- a/netwerk/dns/mdns/libmdns/moz.build +++ b/netwerk/dns/mdns/libmdns/moz.build @@ -10,6 +10,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': 'nsDNSServiceDiscovery.manifest', ] + EXTRA_JS_MODULES += [ + 'MulticastDNSAndroid.jsm', + 'MulticastDNSFallback.jsm', + ] + elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' or \ (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16'): UNIFIED_SOURCES += [ diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js index 5f700dd700..ef02736ceb 100644 --- a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js @@ -5,9 +5,15 @@ const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; -Cu.import("resource://gre/modules/MulticastDNS.jsm"); +Cu.import('resource://gre/modules/Services.jsm'); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +if (Services.prefs.getBoolPref("network.mdns.use_js_fallback")) { + Cu.import("resource://gre/modules/MulticastDNSFallback.jsm"); +} else { + Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm"); +} + const DNSSERVICEDISCOVERY_CID = Components.ID("{f9346d98-f27a-4e89-b744-493843416480}"); const DNSSERVICEDISCOVERY_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1"; const DNSSERVICEINFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1"; @@ -135,7 +141,7 @@ nsDNSServiceDiscovery.prototype = { this.stopDiscovery = true; return; } - this.mdns.stopDiscovery(aServiceType, this); + this.mdns.stopDiscovery(aServiceType, listener); }).bind(listener) }; }, @@ -153,7 +159,7 @@ nsDNSServiceDiscovery.prototype = { this.stopRegistration = true; return; } - this.mdns.unregisterService(aServiceInfo, this); + this.mdns.unregisterService(aServiceInfo, listener); }).bind(listener) }; }, diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build index 3d06955a88..727cd102a4 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -79,3 +79,10 @@ else: UNIFIED_SOURCES += [ 'nameprep.c', ] + +if CONFIG['_MSC_VER']: + # This is intended as a temporary hack to support building with VS2015. + # icu\source\common\unicode/ucasemap.h(93): warning C4577: + # 'noexcept' used with no exception handling mode specified; + # termination on exception is not guaranteed. Specify /EHsc from unified dns + CXXFLAGS += ['-wd4577'] diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp index 74468b2aec..bcee5b13a1 100644 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -406,7 +406,8 @@ HostDB_MatchEntry(const PLDHashEntryHdr *entry, const nsHostDBEnt *he = static_cast(entry); const nsHostKey *hk = static_cast(key); - return !strcmp(he->rec->host, hk->host) && + return !strcmp(he->rec->host ? he->rec->host : "", + hk->host ? hk->host : "") && RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) && he->rec->af == hk->af && !strcmp(he->rec->netInterface, hk->netInterface); diff --git a/netwerk/ipc/NeckoCommon.h b/netwerk/ipc/NeckoCommon.h index 15bf5da5b1..0d3c259401 100644 --- a/netwerk/ipc/NeckoCommon.h +++ b/netwerk/ipc/NeckoCommon.h @@ -90,13 +90,8 @@ IsNeckoChild() static bool amChild = false; if (!didCheck) { - // This allows independent necko-stacks (instead of single stack in chrome) - // to still be run. - // TODO: Remove eventually when no longer supported (bug 571126) - const char * e = PR_GetEnv("NECKO_SEPARATE_STACKS"); - if (!e) - amChild = XRE_IsContentProcess(); didCheck = true; + amChild = (XRE_GetProcessType() == GeckoProcessType_Content); } return amChild; } diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp index 92b437748b..c5546b7a1c 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -1005,6 +1005,17 @@ NeckoParent::OfflineNotification(nsISupports *aSubject) } + // XPCShells don't have any TabParents + // Just send the ipdl message to the child process. + if (!UsingNeckoIPCSecurity()) { + bool offline = false; + gIOService->IsAppOffline(targetAppId, &offline); + if (!SendAppOfflineStatus(targetAppId, offline)) { + printf_stderr("NeckoParent: " + "SendAppOfflineStatus failed for targetAppId: %u\n", targetAppId); + } + } + return NS_OK; } diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp index 53cc509889..586aa6109a 100644 --- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -564,8 +564,7 @@ Http2Stream::GenerateOpen() firstFrameFlags |= Http2Session::kFlag_END_STREAM; } else if (head->IsPost() || head->IsPut() || - head->IsConnect() || - head->IsOptions()) { + head->IsConnect()) { // place fin in a data frame even for 0 length messages for iterop } else if (!mRequestBodyLenRemaining) { // for other HTTP extension methods, rely on the content-length diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h index d66d9ceabf..7d641287d7 100644 --- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -184,7 +184,6 @@ private: nsCOMPtr mRedirectChannelChild; RefPtr mInterceptListener; RefPtr mSynthesizedResponsePump; - nsAutoPtr mSynthesizedResponseHead; nsCOMPtr mSynthesizedInput; int64_t mSynthesizedStreamLength; diff --git a/netwerk/protocol/http/HttpChannelParentListener.cpp b/netwerk/protocol/http/HttpChannelParentListener.cpp index 5689ccc5d1..ab7e54881b 100644 --- a/netwerk/protocol/http/HttpChannelParentListener.cpp +++ b/netwerk/protocol/http/HttpChannelParentListener.cpp @@ -148,6 +148,14 @@ HttpChannelParentListener::AsyncOnChannelRedirect( { nsresult rv; + nsCOMPtr activeRedirectingChannel = + do_QueryInterface(mNextListener); + if (!activeRedirectingChannel) { + NS_ERROR("Channel got a redirect response, but doesn't implement " + "nsIParentRedirectingChannel to handle it."); + return NS_ERROR_NOT_IMPLEMENTED; + } + // Register the new channel and obtain id for it nsCOMPtr registrar = do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv); @@ -158,13 +166,6 @@ HttpChannelParentListener::AsyncOnChannelRedirect( LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId)); - nsCOMPtr activeRedirectingChannel = - do_QueryInterface(mNextListener); - if (!activeRedirectingChannel) { - NS_RUNTIMEABORT("Channel got a redirect response, but doesn't implement " - "nsIParentRedirectingChannel to handle it."); - } - return activeRedirectingChannel->StartRedirect(mRedirectChannelId, newChannel, redirectFlags, diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp index c0ec4ba496..597c653a53 100644 --- a/netwerk/protocol/http/InterceptedChannel.cpp +++ b/netwerk/protocol/http/InterceptedChannel.cpp @@ -198,6 +198,9 @@ InterceptedChannelChrome::ResetInterception() nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL); NS_ENSURE_SUCCESS(rv, rv); + mResponseBody->Close(); + mResponseBody = nullptr; + mReleaseHandle = nullptr; mChannel = nullptr; return NS_OK; @@ -230,6 +233,11 @@ InterceptedChannelChrome::FinishSynthesizedResponse(const nsACString& aFinalURLS return NS_ERROR_NOT_AVAILABLE; } + // Make sure the cache entry's output stream is always closed. If the + // channel was intercepted with a null-body response then its possible + // the synthesis completed without a stream copy operation. + mResponseBody->Close(); + mReportCollector->FlushConsoleReports(mChannel); EnsureSynthesizedResponse(); @@ -382,6 +390,7 @@ InterceptedChannelContent::ResetInterception() mReportCollector->FlushConsoleReports(mChannel); + mResponseBody->Close(); mResponseBody = nullptr; mSynthesizedInput = nullptr; @@ -418,6 +427,11 @@ InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURL return NS_ERROR_NOT_AVAILABLE; } + // Make sure the body output stream is always closed. If the channel was + // intercepted with a null-body response then its possible the synthesis + // completed without a stream copy operation. + mResponseBody->Close(); + mReportCollector->FlushConsoleReports(mChannel); EnsureSynthesizedResponse(); diff --git a/netwerk/protocol/http/PackagedAppService.cpp b/netwerk/protocol/http/PackagedAppService.cpp index 7c23714d57..57f825c811 100644 --- a/netwerk/protocol/http/PackagedAppService.cpp +++ b/netwerk/protocol/http/PackagedAppService.cpp @@ -727,7 +727,7 @@ PackagedAppService::PackagedAppDownloader::AddCallback(nsIURI *aURI, { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe"); nsAutoCString spec; - aURI->GetAsciiSpec(spec); + aURI->GetSpecIgnoringRef(spec); LogURI("PackagedAppDownloader::AddCallback", this, aURI); LOG(("[%p] > callback: %p\n", this, aCallback)); @@ -1037,7 +1037,7 @@ PackagedAppService::GetResource(nsIChannel *aChannel, nsCOMPtr loadInfo = aChannel->GetLoadInfo(); nsCOMPtr uri; - rv = principal->GetURI(getter_AddRefs(uri)); + rv = aChannel->GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(NS_FAILED(rv))) { LOG(("[%p] > Error calling GetURI rv=%X\n", this, rv)); return rv; diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp index ad79c19f25..5e8f807e0a 100644 --- a/netwerk/protocol/http/nsHttp.cpp +++ b/netwerk/protocol/http/nsHttp.cpp @@ -12,6 +12,7 @@ #include "mozilla/Mutex.h" #include "mozilla/HashFunctions.h" #include "nsCRT.h" +#include namespace mozilla { namespace net { @@ -294,19 +295,25 @@ nsHttp::FindToken(const char *input, const char *token, const char *seps) bool nsHttp::ParseInt64(const char *input, const char **next, int64_t *r) { - const char *start = input; - *r = 0; - while (*input >= '0' && *input <= '9') { - int64_t next = 10 * (*r) + (*input - '0'); - if (next < *r) // overflow? - return false; - *r = next; - ++input; - } - if (input == start) // nothing parsed? + MOZ_ASSERT(input); + MOZ_ASSERT(r); + + char *end = nullptr; + errno = 0; // Clear errno to make sure its value is set by strtoll + int64_t value = strtoll(input, &end, /* base */ 10); + + // Fail if: - the parsed number overflows. + // - the end points to the start of the input string. + // - we parsed a negative value. Consumers don't expect that. + if (errno != 0 || end == input || value < 0) { + LOG(("nsHttp::ParseInt64 value=%ld errno=%d", value, errno)); return false; - if (next) - *next = input; + } + + if (next) { + *next = end; + } + *r = value; return true; } diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp index ff342ea09c..c132f40055 100644 --- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp +++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp @@ -824,9 +824,7 @@ nsHttpChannelAuthProvider::BlockPrompt() nsCOMPtr chanInternal = do_QueryInterface(mAuthChannel); MOZ_ASSERT(chanInternal); - bool skipAuthentication = false; - nsresult rv = chanInternal->GetBlockAuthPrompt(&skipAuthentication); - if (NS_SUCCEEDED(rv) && skipAuthentication) { + if (chanInternal->GetBlockAuthPrompt()) { return true; } diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 4961231a46..d94fdf4bc8 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -185,7 +185,6 @@ nsHttpConnectionMgr::Shutdown() // wait for shutdown event to complete while (!shutdownWrapper->mBool) { - fprintf(stderr, "nsHttpConnectionMgr::Shutdown() ProcessNextEvent\n"); NS_ProcessNextEvent(NS_GetCurrentThread()); } diff --git a/netwerk/protocol/http/nsHttpResponseHead.cpp b/netwerk/protocol/http/nsHttpResponseHead.cpp index 1fb68c3371..61ae865848 100644 --- a/netwerk/protocol/http/nsHttpResponseHead.cpp +++ b/netwerk/protocol/http/nsHttpResponseHead.cpp @@ -415,7 +415,7 @@ nsHttpResponseHead::ComputeCurrentAge(uint32_t now, // // freshnessLifetime = expires_value - date_value // -// freshnessLifetime = (date_value - last_modified_value) * 0.10 +// freshnessLifetime = min(one-week,(date_value - last_modified_value) * 0.10) // // freshnessLifetime = 0 // @@ -463,6 +463,8 @@ nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t *result) const if (date2 <= date) { // this only makes sense if last-modified is actually in the past *result = (date - date2) / 10; + const uint32_t kOneWeek = 60 * 60 * 24 * 7; + *result = std::min(kOneWeek, *result); return NS_OK; } } diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index ee4d814830..84d0ffc00f 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -39,7 +39,7 @@ interface nsIHttpUpgradeListener : nsISupports * using any feature exposed by this interface, be aware that this interface * will change and you will be broken. You have been warned. */ -[scriptable, uuid(01b8296a-e206-4e5f-acab-82bd8b6a900c)] +[builtinclass, scriptable, uuid(4e28263d-1e03-46f4-aa5c-9512f91957f9)] interface nsIHttpChannelInternal : nsISupports { /** @@ -283,5 +283,6 @@ interface nsIHttpChannelInternal : nsISupports * authentication failure, that failure will be propagated to the channel * listener. Must be called before opening the channel, otherwise throws. */ + [infallible] attribute boolean blockAuthPrompt; }; diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp index b1e2648dc7..da297d1de9 100644 --- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp +++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp @@ -18,6 +18,7 @@ #include "nsIStreamConverterService.h" #include "nsIPipe.h" #include "nsNetUtil.h" +#include "LoadInfo.h" namespace mozilla { @@ -103,8 +104,9 @@ ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI, const char* kToType = "text/css"; nsCOMPtr inputStream; - if (aLoadInfo && aLoadInfo->GetSecurityMode()) { - // Certain security checks require an async channel. + if (aLoadInfo && + aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) { + // If the channel needs to enforce CORS, we need to open the channel async. nsCOMPtr outputStream; rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(outputStream), @@ -121,13 +123,20 @@ ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI, aURI, getter_AddRefs(converter)); NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr loadInfo = + static_cast(aLoadInfo)->CloneForNewRequest(); + (*result)->SetLoadInfo(loadInfo); + rv = (*result)->AsyncOpen2(converter); } else { - // Stylesheet loads for extension content scripts require a sync channel, - // but fortunately do not invoke security checks. + // Stylesheet loads for extension content scripts require a sync channel. nsCOMPtr sourceStream; - rv = (*result)->Open(getter_AddRefs(sourceStream)); + if (aLoadInfo && aLoadInfo->GetEnforceSecurity()) { + rv = (*result)->Open2(getter_AddRefs(sourceStream)); + } else { + rv = (*result)->Open(getter_AddRefs(sourceStream)); + } NS_ENSURE_SUCCESS(rv, rv); rv = convService->Convert(sourceStream, kFromType, kToType, diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp index 839fb9513d..c62e6ad8a3 100644 --- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp +++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp @@ -352,28 +352,40 @@ SubstitutingProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result) return NS_OK; } - // Unescape the path so we can perform some checks on it. - nsAutoCString unescapedPath(path); - NS_UnescapeURL(unescapedPath); - - // Don't misinterpret the filepath as an absolute URI. - if (unescapedPath.FindChar(':') != -1) - return NS_ERROR_MALFORMED_URI; - - if (unescapedPath.FindChar('\\') != -1) - return NS_ERROR_MALFORMED_URI; - - const char *p = path.get() + 1; // path always starts with a slash - NS_ASSERTION(*(p-1) == '/', "Path did not begin with a slash!"); - - if (*p == '/') - return NS_ERROR_MALFORMED_URI; - nsCOMPtr baseURI; rv = GetSubstitution(host, getter_AddRefs(baseURI)); if (NS_FAILED(rv)) return rv; - rv = baseURI->Resolve(nsDependentCString(p, path.Length()-1), result); + // Unescape the path so we can perform some checks on it. + nsCOMPtr url = do_QueryInterface(uri); + if (!url) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString unescapedPath; + rv = url->GetFilePath(unescapedPath); + if (NS_FAILED(rv)) return rv; + + NS_UnescapeURL(unescapedPath); + if (unescapedPath.FindChar('\\') != -1) { + return NS_ERROR_MALFORMED_URI; + } + + // Some code relies on an empty path resolving to a file rather than a + // directory. + NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'"); + if (path.Length() == 1) { + rv = baseURI->GetSpec(result); + } else { + // Make sure we always resolve the path as file-relative to our target URI. + path.InsertLiteral(".", 0); + + rv = baseURI->Resolve(path, result); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) { nsAutoCString spec; diff --git a/netwerk/protocol/res/nsResProtocolHandler.cpp b/netwerk/protocol/res/nsResProtocolHandler.cpp index a5d5572aff..cbd46546fe 100644 --- a/netwerk/protocol/res/nsResProtocolHandler.cpp +++ b/netwerk/protocol/res/nsResProtocolHandler.cpp @@ -21,46 +21,27 @@ using mozilla::dom::ContentParent; using mozilla::LogLevel; using mozilla::Unused; -#define kAPP NS_LITERAL_CSTRING("app") -#define kGRE NS_LITERAL_CSTRING("gre") +#define kAPP "app" +#define kGRE "gre" nsresult nsResProtocolHandler::Init() { nsresult rv; - nsAutoCString appURI, greURI; - rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appURI); + rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, mAppURI); NS_ENSURE_SUCCESS(rv, rv); - rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greURI); + rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, mGREURI); NS_ENSURE_SUCCESS(rv, rv); - // - // make resource:/// point to the application directory or omnijar - // - nsCOMPtr uri; - rv = NS_NewURI(getter_AddRefs(uri), appURI.Length() ? appURI : greURI); - NS_ENSURE_SUCCESS(rv, rv); - - rv = SetSubstitution(EmptyCString(), uri); - NS_ENSURE_SUCCESS(rv, rv); - - // - // make resource://app/ point to the application directory or omnijar - // - rv = SetSubstitution(kAPP, uri); - NS_ENSURE_SUCCESS(rv, rv); - - // - // make resource://gre/ point to the GRE directory - // - if (appURI.Length()) { // We already have greURI in uri if appURI.Length() is 0. - rv = NS_NewURI(getter_AddRefs(uri), greURI); - NS_ENSURE_SUCCESS(rv, rv); + // mozilla::Omnijar::GetURIString always returns a string ending with /, + // and we want to remove it. + mGREURI.Truncate(mGREURI.Length() - 1); + if (mAppURI.Length()) { + mAppURI.Truncate(mAppURI.Length() - 1); + } else { + mAppURI = mGREURI; } - rv = SetSubstitution(kGRE, uri); - NS_ENSURE_SUCCESS(rv, rv); - //XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir... // but once I finish multiple chrome registration I'm not sure that it is needed @@ -83,20 +64,36 @@ NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler) nsresult nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result) { - // try invoking the directory service for "resource:root" + nsAutoCString uri; - nsAutoCString key; - key.AssignLiteral("resource:"); - key.Append(root); - - nsCOMPtr file; - nsresult rv = NS_GetSpecialDirectory(key.get(), getter_AddRefs(file)); - if (NS_FAILED(rv)) - return NS_ERROR_NOT_AVAILABLE; - - rv = IOService()->NewFileURI(file, result); - if (NS_FAILED(rv)) + if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), uri)) { return NS_ERROR_NOT_AVAILABLE; + } - return NS_OK; + return NS_NewURI(result, uri); +} + +bool +nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost, + const nsACString& aPath, + nsACString& aResult) +{ + if (aHost.Equals("") || aHost.Equals(kAPP)) { + aResult.Assign(mAppURI); + } else if (aHost.Equals(kGRE)) { + aResult.Assign(mGREURI); + } else { + return false; + } + aResult.Append(aPath); + return true; +} + +nsresult +nsResProtocolHandler::SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI) +{ + MOZ_ASSERT(!aRoot.Equals("")); + MOZ_ASSERT(!aRoot.Equals(kAPP)); + MOZ_ASSERT(!aRoot.Equals(kGRE)); + return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI); } diff --git a/netwerk/protocol/res/nsResProtocolHandler.h b/netwerk/protocol/res/nsResProtocolHandler.h index d3e1d0d303..58588d922d 100644 --- a/netwerk/protocol/res/nsResProtocolHandler.h +++ b/netwerk/protocol/res/nsResProtocolHandler.h @@ -23,7 +23,6 @@ public: NS_DECL_NSIRESPROTOCOLHANDLER NS_FORWARD_NSIPROTOCOLHANDLER(mozilla::SubstitutingProtocolHandler::) - NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(mozilla::SubstitutingProtocolHandler::) nsResProtocolHandler() : SubstitutingProtocolHandler("resource", URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE, @@ -32,9 +31,33 @@ public: nsresult Init(); + NS_IMETHOD SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI) override; + + NS_IMETHOD GetSubstitution(const nsACString& aRoot, nsIURI** aResult) override + { + return mozilla::SubstitutingProtocolHandler::GetSubstitution(aRoot, aResult); + } + + NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override + { + return mozilla::SubstitutingProtocolHandler::HasSubstitution(aRoot, aResult); + } + + NS_IMETHOD ResolveURI(nsIURI *aResURI, nsACString& aResult) override + { + return mozilla::SubstitutingProtocolHandler::ResolveURI(aResURI, aResult); + } + protected: nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override; virtual ~nsResProtocolHandler() {} + + bool ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, + nsACString& aResult) override; + +private: + nsCString mAppURI; + nsCString mGREURI; }; #endif /* nsResProtocolHandler_h___ */ diff --git a/netwerk/streamconv/converters/nsDirIndex.cpp b/netwerk/streamconv/converters/nsDirIndex.cpp index 8729cf2081..0ad77805ac 100644 --- a/netwerk/streamconv/converters/nsDirIndex.cpp +++ b/netwerk/streamconv/converters/nsDirIndex.cpp @@ -10,7 +10,8 @@ NS_IMPL_ISUPPORTS(nsDirIndex, nsDirIndex::nsDirIndex() : mType(TYPE_UNKNOWN), mSize(UINT64_MAX), - mLastModified(-1) { + mLastModified(-1LL) +{ } nsDirIndex::~nsDirIndex() {} @@ -18,9 +19,7 @@ nsDirIndex::~nsDirIndex() {} NS_IMETHODIMP nsDirIndex::GetType(uint32_t* aType) { - if (!aType) { - return NS_ERROR_NULL_POINTER; - } + NS_ENSURE_ARG_POINTER(aType); *aType = mType; return NS_OK; @@ -34,7 +33,10 @@ nsDirIndex::SetType(uint32_t aType) } NS_IMETHODIMP -nsDirIndex::GetContentType(char* *aContentType) { +nsDirIndex::GetContentType(char* *aContentType) +{ + NS_ENSURE_ARG_POINTER(aContentType); + *aContentType = ToNewCString(mContentType); if (!*aContentType) return NS_ERROR_OUT_OF_MEMORY; @@ -43,13 +45,17 @@ nsDirIndex::GetContentType(char* *aContentType) { } NS_IMETHODIMP -nsDirIndex::SetContentType(const char* aContentType) { +nsDirIndex::SetContentType(const char* aContentType) +{ mContentType = aContentType; return NS_OK; } NS_IMETHODIMP -nsDirIndex::GetLocation(char* *aLocation) { +nsDirIndex::GetLocation(char* *aLocation) +{ + NS_ENSURE_ARG_POINTER(aLocation); + *aLocation = ToNewCString(mLocation); if (!*aLocation) return NS_ERROR_OUT_OF_MEMORY; @@ -58,13 +64,17 @@ nsDirIndex::GetLocation(char* *aLocation) { } NS_IMETHODIMP -nsDirIndex::SetLocation(const char* aLocation) { +nsDirIndex::SetLocation(const char* aLocation) +{ mLocation = aLocation; return NS_OK; } NS_IMETHODIMP -nsDirIndex::GetDescription(char16_t* *aDescription) { +nsDirIndex::GetDescription(char16_t* *aDescription) +{ + NS_ENSURE_ARG_POINTER(aDescription); + *aDescription = ToNewUnicode(mDescription); if (!*aDescription) return NS_ERROR_OUT_OF_MEMORY; @@ -73,7 +83,8 @@ nsDirIndex::GetDescription(char16_t* *aDescription) { } NS_IMETHODIMP -nsDirIndex::SetDescription(const char16_t* aDescription) { +nsDirIndex::SetDescription(const char16_t* aDescription) +{ mDescription.Assign(aDescription); return NS_OK; } @@ -81,9 +92,7 @@ nsDirIndex::SetDescription(const char16_t* aDescription) { NS_IMETHODIMP nsDirIndex::GetSize(int64_t* aSize) { - if (!aSize) { - return NS_ERROR_NULL_POINTER; - } + NS_ENSURE_ARG_POINTER(aSize); *aSize = mSize; return NS_OK; @@ -99,9 +108,7 @@ nsDirIndex::SetSize(int64_t aSize) NS_IMETHODIMP nsDirIndex::GetLastModified(PRTime* aLastModified) { - if (!aLastModified) { - return NS_ERROR_NULL_POINTER; - } + NS_ENSURE_ARG_POINTER(aLastModified); *aLastModified = mLastModified; return NS_OK; diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp index 999cc35366..2b296f6e7a 100644 --- a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp +++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp @@ -134,7 +134,7 @@ nsHTTPCompressConv::OnStopRequest(nsIRequest* request, nsISupports *aContext, if (fpChannel && !isPending) { fpChannel->ForcePending(true); } - if (mBrotli && mBrotli->mTotalOut == 0 && !BrotliDecoderIsFinished(&mBrotli->mState)) { + if (mBrotli && (mBrotli->mTotalOut == 0) && !BrotliDecoderIsFinished(&mBrotli->mState)) { status = NS_ERROR_INVALID_CONTENT_ENCODING; } if (fpChannel && !isPending) { diff --git a/netwerk/streamconv/converters/nsIndexedToHTML.cpp b/netwerk/streamconv/converters/nsIndexedToHTML.cpp index 2a46bf78ff..ca7adcbed0 100644 --- a/netwerk/streamconv/converters/nsIndexedToHTML.cpp +++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp @@ -800,7 +800,7 @@ nsIndexedToHTML::OnIndexAvailable(nsIRequest *aRequest, PRTime t; aIndex->GetLastModified(&t); - if (t == -1) { + if (t == -1LL) { pushBuffer.AppendLiteral(">\n "); } else { pushBuffer.AppendLiteral(" sortable-data=\""); diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.cpp b/netwerk/streamconv/converters/nsMultiMixedConv.cpp index 95fef0f2e0..faf135da5a 100644 --- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp +++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp @@ -666,6 +666,10 @@ nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context, // Push the cursor to the token so that the while loop below will // find token from the right position. cursor = tokenPos; + + // Update bufLen to exlude the preamble. Otherwise, the first + // |SendData| would claim longer buffer length. + bufLen -= mPreamble.Length(); } } else { // If the boundary was set in the header, diff --git a/netwerk/test/unit/test_bug337744.js b/netwerk/test/unit/test_bug337744.js index a2422a8df6..bcf8cdb749 100644 --- a/netwerk/test/unit/test_bug337744.js +++ b/netwerk/test/unit/test_bug337744.js @@ -1,41 +1,114 @@ /* verify that certain invalid URIs are not parsed by the resource protocol handler */ +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + const specs = [ - "resource:////", - "resource:///http://www.mozilla.org/", - "resource:///file:///", - "resource:///..\\", - "resource:///..\\..\\", - "resource:///..%5C", - "resource:///..%5c" + "resource://res-test//", + "resource://res-test/?foo=http:", + "resource://res-test/?foo=" + encodeURIComponent("http://example.com/"), + "resource://res-test/?foo=" + encodeURIComponent("x\\y"), + "resource://res-test/..%2F", + "resource://res-test/..%2f", + "resource://res-test/..%2F..", + "resource://res-test/..%2f..", + "resource://res-test/../../", + "resource://res-test/http://www.mozilla.org/", + "resource://res-test/file:///", ]; -function check_for_exception(spec) +const error_specs = [ + "resource://res-test/..\\", + "resource://res-test/..\\..\\", + "resource://res-test/..%5C", + "resource://res-test/..%5c", +]; + +// Create some fake principal that has not enough +// privileges to access any resource: uri. +var uri = NetUtil.newURI("http://www.example.com", null, null); +var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {}); + +function get_channel(spec) { - var ios = - Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); + var channelURI = NetUtil.newURI(spec, null, null); + + var channel = NetUtil.newChannel({ + uri: NetUtil.newURI(spec, null, null), + loadingPrincipal: principal, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER + }); try { - var channel = ios.newChannel2(spec, - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); + channel.asyncOpen2(null); + ok(false, "asyncOpen2() of URI: " + spec + "should throw"); } catch (e) { - return; + // make sure we get the right error code in the exception + // ERROR code for NS_ERROR_DOM_BAD_URI is 1012 + equal(e.code, 1012); } - do_throw("Successfully opened invalid URI: '" + spec + "'"); + try { + channel.open2(); + ok(false, "Open2() of uri: " + spec + "should throw"); + } + catch (e) { + // make sure we get the right error code in the exception + // ERROR code for NS_ERROR_DOM_BAD_URI is 1012 + equal(e.code, 1012); + } + + return channel; +} + +function check_safe_resolution(spec, rootURI) +{ + do_print(`Testing URL "${spec}"`); + + let channel = get_channel(spec); + + ok(channel.name.startsWith(rootURI), `URL resolved safely to ${channel.name}`); + ok(!/%2f/i.test(channel.name), `URL contains no escaped / characters`); +} + +function check_resolution_error(spec) +{ + try { + get_channel(spec); + ok(false, "Expected an error"); + } catch (e) { + equal(e.result, Components.results.NS_ERROR_MALFORMED_URI, + "Expected a malformed URI error"); + } } function run_test() { + // resource:/// and resource://gre/ are resolved specially, so we need + // to create a temporary resource package to test the standard logic + // with. + + let resProto = Cc['@mozilla.org/network/protocol;1?name=resource'].getService(Ci.nsIResProtocolHandler); + let rootFile = Services.dirsvc.get("GreD", Ci.nsIFile); + let rootURI = Services.io.newFileURI(rootFile); + + resProto.setSubstitution("res-test", rootURI); + do_register_cleanup(() => { + resProto.setSubstitution("res-test", null); + }); + + let baseRoot = resProto.resolveURI(Services.io.newURI("resource:///", null, null)); + let greRoot = resProto.resolveURI(Services.io.newURI("resource://gre/", null, null)); + for (var spec of specs) { - check_for_exception(spec); + check_safe_resolution(spec, rootURI.spec); + check_safe_resolution(spec.replace("res-test", ""), baseRoot); + check_safe_resolution(spec.replace("res-test", "gre"), greRoot); + } + + for (var spec of error_specs) { + check_resolution_error(spec); } } diff --git a/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js index 1dad338214..256c6d9d38 100644 --- a/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js +++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js @@ -4,7 +4,7 @@ This test exercises the CacheFileContextEvictor::WasEvicted API and code using i - We store 10+10 (pinned and non-pinned) entries to the cache, wait for them being written. - Then we purge the memory pools. -- Now the IO thread is suspended on the EVICT (8) level to prevent actual deletion of the files. +- Now the IO thread is suspended on the EVICT (7) level to prevent actual deletion of the files. - Index is disabled. - We do clear() of the cache, this creates the "ce_*" file and posts to the EVICT level the eviction loop mechanics. @@ -47,9 +47,9 @@ function run_test() // (1), here we start + log_("first set of opens"); var i; for (i = 0; i < kENTRYCOUNT; ++i) { - log_("first set of opens"); // Callbacks 1-20 mc.add(); @@ -72,7 +72,7 @@ function run_test() log_("after purge"); // Prevent the I/O thread from evicting physically the data. We first want to re-open the entries. // This deterministically emulates a slow hard drive. - testingInterface.suspendCacheIOThread(8); + testingInterface.suspendCacheIOThread(7); log_("clearing"); // Now clear everything except pinned. Stores the "ce_*" file and schedules background eviction. @@ -96,7 +96,9 @@ function run_test() // an early check on CacheIOThread::YieldAndRerun() in that method. // CacheFileIOManager::OpenFileInternal should now run and CacheFileContextEvictor::WasEvicted // should be checked on. + log_("resuming"); testingInterface.resumeCacheIOThread(); + log_("resumed"); mc.fired(); // Finishes this test } diff --git a/netwerk/test/unit/test_multipart_streamconv_application_package.js b/netwerk/test/unit/test_multipart_streamconv_application_package.js index 2a592c76b1..ca7907159a 100644 --- a/netwerk/test/unit/test_multipart_streamconv_application_package.js +++ b/netwerk/test/unit/test_multipart_streamconv_application_package.js @@ -76,11 +76,17 @@ function contentHandler_type_missing(metadata, response) response.bodyOutputStream.write(body, body.length); } -function contentHandler_with_package_header(metadata, response) +function contentHandler_with_package_header(chunkSize, metadata, response) { response.setHeader("Content-Type", 'application/package'); var body = testData.packageHeader + testData.getData(); - response.bodyOutputStream.write(body, body.length); + + response.bodyOutputStream.write(body.substring(0,chunkSize), chunkSize); + response.processAsync(); + do_timeout(5, function() { + response.bodyOutputStream.write(body.substring(chunkSize), body.length-chunkSize); + response.finish(); + }); } var testData = { @@ -244,7 +250,7 @@ function test_multipart_content_type_other() { chan.asyncOpen(conv, null); } -function test_multipart_package_header() { +function test_multipart_package_header(aChunkSize) { var streamConv = Cc["@mozilla.org/streamConverters;1"] .getService(Ci.nsIStreamConverterService); @@ -253,10 +259,29 @@ function test_multipart_package_header() { new multipartListener(testData, false, true), null); - var chan = make_channel(uri + "/multipart5"); + var chan = make_channel(uri + "/multipart5_" + aChunkSize); chan.asyncOpen(conv, null); } +// Bug 1212223 - Test multipart with package header and different chunk size. +// Use explict function name to make the test case log more readable. + +function test_multipart_package_header_50() { + return test_multipart_package_header(50); +} + +function test_multipart_package_header_100() { + return test_multipart_package_header(100); +} + +function test_multipart_package_header_150() { + return test_multipart_package_header(150); +} + +function test_multipart_package_header_200() { + return test_multipart_package_header(200); +} + function run_test() { httpserver = new HttpServer(); @@ -264,7 +289,13 @@ function run_test() httpserver.registerPathHandler("/multipart2", contentHandler_with_boundary); httpserver.registerPathHandler("/multipart3", contentHandler_chunked_headers); httpserver.registerPathHandler("/multipart4", contentHandler_type_missing); - httpserver.registerPathHandler("/multipart5", contentHandler_with_package_header); + + // Bug 1212223 - Test multipart with package header and different chunk size. + httpserver.registerPathHandler("/multipart5_50", contentHandler_with_package_header.bind(null, 50)); + httpserver.registerPathHandler("/multipart5_100", contentHandler_with_package_header.bind(null, 100)); + httpserver.registerPathHandler("/multipart5_150", contentHandler_with_package_header.bind(null, 150)); + httpserver.registerPathHandler("/multipart5_200", contentHandler_with_package_header.bind(null, 200)); + httpserver.start(-1); run_next_test(); @@ -274,4 +305,9 @@ add_test(test_multipart); add_test(test_multipart_with_boundary); add_test(test_multipart_chunked_headers); add_test(test_multipart_content_type_other); -add_test(test_multipart_package_header); + +// Bug 1212223 - Test multipart with package header and different chunk size. +add_test(test_multipart_package_header_50); +add_test(test_multipart_package_header_100); +add_test(test_multipart_package_header_150); +add_test(test_multipart_package_header_200); diff --git a/netwerk/test/unit/test_packaged_app_service.js b/netwerk/test/unit/test_packaged_app_service.js index 0af6598488..6c218abd56 100644 --- a/netwerk/test/unit/test_packaged_app_service.js +++ b/netwerk/test/unit/test_packaged_app_service.js @@ -247,6 +247,8 @@ function run_test() add_test(test_worse_package_4); add_test(test_worse_package_5); + add_test(test_request_has_ref); + // run tests run_next_test(); } @@ -334,6 +336,13 @@ function test_updated_package() { new packagedResourceListener(testData.content[0].data.replace(/\.\.\./g, 'xxx'))); } +// This tests that requested URI with reference should still work. +function test_request_has_ref() { + packagePath = "/package"; + let url = uri + packagePath + "!//index.html#Ref"; + paservice.getResource(getChannelForURL(url), cacheListener); +} + // ---------------------------------------------------------------------------- // This listener checks that the requested resources are not returned diff --git a/netwerk/test/unit_ipc/child_app_offline_notifications.js b/netwerk/test/unit_ipc/child_app_offline_notifications.js new file mode 100644 index 0000000000..870c22b397 --- /dev/null +++ b/netwerk/test/unit_ipc/child_app_offline_notifications.js @@ -0,0 +1,43 @@ +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function is_app_offline(appId) { + let ioservice = Cc['@mozilla.org/network/io-service;1']. + getService(Ci.nsIIOService); + return ioservice.isAppOffline(appId); +} + +var events_observed_no = 0; + +// Holds the last observed app-offline event +var info = null; +function observer(aSubject, aTopic, aData) { + events_observed_no++; + info = aSubject.QueryInterface(Ci.nsIAppOfflineInfo); + dump("ChildObserver - subject: {" + aSubject.appId + ", " + aSubject.mode + "} "); +} + +// Add observer for the app-offline notification +function run_test() { + Services.obs.addObserver(observer, "network:app-offline-status-changed", false); +} + +// Chech that the app has the proper offline status +function check_status(appId, status) +{ + do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE); +} + +// Check that the app has the proper offline status +// and that the correct notification has been received +function check_notification_and_status(appId, status) { + do_check_eq(info.appId, appId); + do_check_eq(info.mode, status); + do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE); +} + +// Remove the observer from the child process +function finished() { + Services.obs.removeObserver(observer, "network:app-offline-status-changed"); + do_check_eq(events_observed_no, 2); +} diff --git a/netwerk/test/unit_ipc/test_app_offline_notifications.js b/netwerk/test/unit_ipc/test_app_offline_notifications.js new file mode 100644 index 0000000000..f4e4b5aa62 --- /dev/null +++ b/netwerk/test/unit_ipc/test_app_offline_notifications.js @@ -0,0 +1,102 @@ +// Checks that app-offline notifications are received in both the parent +// and the child process, and that after receiving the notification +// isAppOffline returns the correct value + + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +var test_index = 0; +var APP_ID = 42; +var events_observed_no = 0; + +function set_app_offline(appId, offline) { + let ioservice = Cc['@mozilla.org/network/io-service;1']. + getService(Ci.nsIIOService); + ioservice.setAppOffline(appId, offline); +} + +function is_app_offline(appId) { + let ioservice = Cc['@mozilla.org/network/io-service;1']. + getService(Ci.nsIIOService); + return ioservice.isAppOffline(appId); +} + +// The expected offline status after running each function, +// and the next function that should be run. + +// test0 test1 test2 test3 test4 +let expected_offline = [ false, true, true, false, false]; +let callbacks = [ test1, test2, test3, test4, finished]; + +function observer(aSubject, aTopic, aData) { + events_observed_no++; + let info = aSubject.QueryInterface(Ci.nsIAppOfflineInfo); + dump("ParentObserver - subject: {" + aSubject.appId + ", " + aSubject.mode + "} " + + "topic: " + aTopic + "\n"); + + // Check that the correct offline status is in place + do_check_eq(is_app_offline(APP_ID), expected_offline[test_index]); + + // Execute the callback for the current test + do_execute_soon(callbacks[test_index]); +} + +function run_test() { + Services.obs.addObserver(observer, "network:app-offline-status-changed", false); + + test_index = 0; + do_check_eq(is_app_offline(APP_ID), expected_offline[test_index]) // The app should be online at first + run_test_in_child("child_app_offline_notifications.js", test0); +} + +// Check that the app is online by default in the child +function test0() { + dump("parent: RUNNING: test0\n"); + test_index = 0; + sendCommand('check_status('+APP_ID+','+Ci.nsIAppOfflineInfo.ONLINE+');\n', test1); +} + +// Set the app OFFLINE +// Check that the notification is emmited in the parent process +// The observer function will execute test2 which does the check in the child +function test1() { + dump("parent: RUNNING: test1\n"); + test_index = 1; + set_app_offline(APP_ID, Ci.nsIAppOfflineInfo.OFFLINE); +} + +// Checks that child process sees the app OFFLINE +function test2() { + dump("parent: RUNNING: test2\n"); + test_index = 2; + sendCommand('check_notification_and_status('+APP_ID+','+Ci.nsIAppOfflineInfo.OFFLINE+');\n', test3); +} + +// Set the app ONLINE +// Chech that the notification is received in the parent +// The observer function will execute test3 and do the check in the child +function test3() { + dump("parent: RUNNING: test3\n"); + test_index = 3; + set_app_offline(APP_ID, Ci.nsIAppOfflineInfo.ONLINE); +} + +// Chech that the app is back online +function test4() { + dump("parent: RUNNING: test4\n"); + test_index = 4; + sendCommand('check_notification_and_status('+APP_ID+','+Ci.nsIAppOfflineInfo.ONLINE+');\n', function() { + // Send command to unregister observer on the child + sendCommand('finished();\n', finished); + }); +} + +// Remove observer and end test +function finished() { + dump("parent: RUNNING: finished\n"); + Services.obs.removeObserver(observer, "network:app-offline-status-changed"); + do_check_eq(events_observed_no, 2); + do_test_finished(); +} diff --git a/netwerk/test/unit_ipc/xpcshell.ini b/netwerk/test/unit_ipc/xpcshell.ini index b018541c73..5f8292ab4b 100644 --- a/netwerk/test/unit_ipc/xpcshell.ini +++ b/netwerk/test/unit_ipc/xpcshell.ini @@ -92,3 +92,4 @@ skip-if = true [test_reply_without_content_type_wrap.js] [test_app_offline_http.js] [test_getHost_wrap.js] +[test_app_offline_notifications.js] diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 9a19d333e7..68ad862f93 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -991,6 +991,8 @@ nsChildView::BackingScaleFactorChanged() } mBackingScaleFactor = newScale; + NSRect frame = [mView frame]; + mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, newScale); if (mWidgetListener && !mWidgetListener->GetXULWindow()) { nsIPresShell* presShell = mWidgetListener->GetPresShell(); @@ -2968,6 +2970,7 @@ RectTextureImage::EndUpdate(bool aKeepSurface) LayoutDeviceIntRegion updateRegion = mUpdateRegion; if (mTextureSize != mBufferSize) { mTextureSize = mBufferSize; + needInit = true; } if (needInit || !CanUploadSubtextures()) { diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm index 719190f055..95f2500ddb 100644 --- a/widget/cocoa/nsLookAndFeel.mm +++ b/widget/cocoa/nsLookAndFeel.mm @@ -169,7 +169,7 @@ nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor) aColor = GetColorFromNSColor([NSColor gridColor]); break; case eColorID_activeborder: - aColor = NS_RGB(0x00,0x00,0x00); + aColor = GetColorFromNSColor([NSColor keyboardFocusIndicatorColor]); break; case eColorID_appworkspace: aColor = NS_RGB(0xFF,0xFF,0xFF); @@ -601,4 +601,4 @@ nsLookAndFeel::RefreshImpl() mUseOverlayScrollbarsCached = false; mAllowOverlayScrollbarsOverlapCached = false; } -} \ No newline at end of file +} diff --git a/widget/gtk/NativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp index 2700ad05fe..48478f6000 100644 --- a/widget/gtk/NativeKeyBindings.cpp +++ b/widget/gtk/NativeKeyBindings.cpp @@ -226,7 +226,7 @@ NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) default: // fallback to multiline editor case in release build - MOZ_ASSERT(false, "aType is invalid or not yet implemented"); + MOZ_FALLTHROUGH_ASSERT("aType is invalid or not yet implemented"); case nsIWidget::NativeKeyBindingsForMultiLineEditor: case nsIWidget::NativeKeyBindingsForRichTextEditor: if (!sInstanceForMultiLineEditor) { diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp index 97674c912a..1b696e4019 100644 --- a/widget/gtk/nsLookAndFeel.cpp +++ b/widget/gtk/nsLookAndFeel.cpp @@ -83,6 +83,8 @@ GetGradientColors(const GValue* aValue, return false; auto pattern = static_cast(g_value_get_boxed(aValue)); + if (!pattern) + return false; // Just picking the lightest and darkest colors as simple samples rather // than trying to blend, which could get messy if there are many stops. diff --git a/xpfe/appshell/nsXULWindow.cpp b/xpfe/appshell/nsXULWindow.cpp index 3c4d6f7985..b824605531 100644 --- a/xpfe/appshell/nsXULWindow.cpp +++ b/xpfe/appshell/nsXULWindow.cpp @@ -1032,6 +1032,13 @@ void nsXULWindow::OnChromeLoaded() ApplyChromeFlags(); SyncAttributesToWidget(); + int32_t specWidth = -1, specHeight = -1; + bool gotSize = false; + + if (!mIgnoreXULSize) { + gotSize = LoadSizeFromXUL(specWidth, specHeight); + } + bool positionSet = !mIgnoreXULPosition; nsCOMPtr parentWindow(do_QueryReferent(mParentWindow)); #if defined(XP_UNIX) && !defined(XP_MACOSX) @@ -1041,11 +1048,18 @@ void nsXULWindow::OnChromeLoaded() if (!parentWindow) positionSet = false; #endif - if (positionSet) - positionSet = LoadPositionFromXUL(); + if (positionSet) { + // We have to do this before sizing the window, because sizing depends + // on the resolution of the screen we're on. But positioning needs to + // know the size so that it can constrain to screen bounds.... as an + // initial guess here, we'll use the specified size (if any). + positionSet = LoadPositionFromXUL(specWidth, specHeight); + } + + if (gotSize) { + SetSpecifiedSize(specWidth, specHeight); + } - if (!mIgnoreXULSize) - LoadSizeFromXUL(); if (mIntrinsicallySized) { // (if LoadSizeFromXUL set the size, mIntrinsicallySized will be false) nsCOMPtr cv; @@ -1060,15 +1074,25 @@ void nsXULWindow::OnChromeLoaded() int32_t width = 0, height = 0; if (NS_SUCCEEDED(cv->GetContentSize(&width, &height))) { treeOwner->SizeShellTo(docShellAsItem, width, height); + // Update specified size for the final LoadPositionFromXUL call. + specWidth = width; + specHeight = height; } } } } + // Now that we have set the window's final size, we can re-do its + // positioning so that it is properly constrained to the screen. + if (positionSet) { + LoadPositionFromXUL(specWidth, specHeight); + } + LoadMiscPersistentAttributesFromXUL(); - if (mCenterAfterLoad && !positionSet) + if (mCenterAfterLoad && !positionSet) { Center(parentWindow, parentWindow ? false : true, false); + } if (mShowAfterLoad) { SetVisibility(true); @@ -1079,7 +1103,10 @@ void nsXULWindow::OnChromeLoaded() mPersistentAttributesMask |= PAD_POSITION | PAD_SIZE | PAD_MISC; } -bool nsXULWindow::LoadPositionFromXUL() +// If aSpecWidth and/or aSpecHeight are > 0, we will use these CSS px sizes +// to fit to the screen when staggering windows; if they're negative, +// we use the window's current size instead. +bool nsXULWindow::LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight) { bool gotPosition = false; @@ -1102,11 +1129,16 @@ bool nsXULWindow::LoadPositionFromXUL() // Convert to global display pixels for consistent window management across // screens with diverse resolutions - double scale = mWindow->GetDesktopToDeviceScale().scale; - currX = NSToIntRound(currX / scale); - currY = NSToIntRound(currY / scale); - currWidth = NSToIntRound(currWidth / scale); - currHeight = NSToIntRound(currHeight / scale); + double devToDesktopScale = 1.0 / mWindow->GetDesktopToDeviceScale().scale; + currX = NSToIntRound(currX * devToDesktopScale); + currY = NSToIntRound(currY * devToDesktopScale); + + // For size, use specified value if > 0, else current value + double devToCSSScale = 1.0 / mWindow->GetDefaultScale().scale; + int32_t cssWidth = + aSpecWidth > 0 ? aSpecWidth : NSToIntRound(currWidth * devToCSSScale); + int32_t cssHeight = + aSpecHeight > 0 ? aSpecHeight : NSToIntRound(currHeight * devToCSSScale); // Obtain the position information from the element. int32_t specX = currX; @@ -1142,7 +1174,7 @@ bool nsXULWindow::LoadPositionFromXUL() } } else { - StaggerPosition(specX, specY, currWidth, currHeight); + StaggerPosition(specX, specY, cssWidth, cssHeight); } } mWindow->ConstrainPosition(false, &specX, &specY); @@ -1153,76 +1185,81 @@ bool nsXULWindow::LoadPositionFromXUL() return gotPosition; } -bool nsXULWindow::LoadSizeFromXUL() +bool +nsXULWindow::LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight) { bool gotSize = false; // if we're the hidden window, don't try to validate our size/position. We're // special. - if (mIsHiddenWindow) + if (mIsHiddenWindow) { return false; + } nsCOMPtr windowElement = GetWindowDOMElement(); NS_ENSURE_TRUE(windowElement, false); - int32_t currWidth = 0; - int32_t currHeight = 0; nsresult errorCode; int32_t temp; - NS_ASSERTION(mWindow, "we expected to have a window already"); - - GetSize(&currWidth, &currHeight); - double displayToDevPx = - mWindow ? mWindow->GetDesktopToDeviceScale().scale : 1.0; - double cssToDevPx = mWindow ? mWindow->GetDefaultScale().scale : 1.0; - currWidth = NSToIntRound(currWidth * displayToDevPx / cssToDevPx); - currHeight = NSToIntRound(currHeight * displayToDevPx / cssToDevPx); - - // Obtain the position and sizing information from the element. - int32_t specWidth = currWidth; - int32_t specHeight = currHeight; + // Obtain the sizing information from the element. + aSpecWidth = 100; + aSpecHeight = 100; nsAutoString sizeString; windowElement->GetAttribute(WIDTH_ATTRIBUTE, sizeString); temp = sizeString.ToInteger(&errorCode); if (NS_SUCCEEDED(errorCode) && temp > 0) { - specWidth = std::max(temp, 100); + aSpecWidth = std::max(temp, 100); gotSize = true; } windowElement->GetAttribute(HEIGHT_ATTRIBUTE, sizeString); temp = sizeString.ToInteger(&errorCode); if (NS_SUCCEEDED(errorCode) && temp > 0) { - specHeight = std::max(temp, 100); + aSpecHeight = std::max(temp, 100); gotSize = true; } - if (gotSize) { - // constrain to screen size - nsCOMPtr domWindow; - GetWindowDOMWindow(getter_AddRefs(domWindow)); - if (domWindow) { - nsCOMPtr screen; - domWindow->GetScreen(getter_AddRefs(screen)); - if (screen) { - int32_t screenWidth; - int32_t screenHeight; - screen->GetAvailWidth(&screenWidth); // CSS pixels - screen->GetAvailHeight(&screenHeight); - if (specWidth > screenWidth) - specWidth = screenWidth; - if (specHeight > screenHeight) - specHeight = screenHeight; - } - } + return gotSize; +} - mIntrinsicallySized = false; - if (specWidth != currWidth || specHeight != currHeight) { - SetSize(specWidth * cssToDevPx, specHeight * cssToDevPx, false); +void +nsXULWindow::SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight) +{ + // constrain to screen size + nsCOMPtr domWindow; + GetWindowDOMWindow(getter_AddRefs(domWindow)); + if (domWindow) { + nsCOMPtr screen; + domWindow->GetScreen(getter_AddRefs(screen)); + if (screen) { + int32_t screenWidth; + int32_t screenHeight; + screen->GetAvailWidth(&screenWidth); // CSS pixels + screen->GetAvailHeight(&screenHeight); + if (aSpecWidth > screenWidth) { + aSpecWidth = screenWidth; + } + if (aSpecHeight > screenHeight) { + aSpecHeight = screenHeight; + } } } - return gotSize; + NS_ASSERTION(mWindow, "we expected to have a window already"); + + int32_t currWidth = 0; + int32_t currHeight = 0; + GetSize(&currWidth, &currHeight); // returns device pixels + + // convert specified values to device pixels, and resize if needed + double cssToDevPx = mWindow ? mWindow->GetDefaultScale().scale : 1.0; + aSpecWidth = NSToIntRound(aSpecWidth * cssToDevPx); + aSpecHeight = NSToIntRound(aSpecHeight * cssToDevPx); + mIntrinsicallySized = false; + if (aSpecWidth != currWidth || aSpecHeight != currHeight) { + SetSize(aSpecWidth, aSpecHeight, false); + } } /* Miscellaneous persistent attributes are attributes named in the @@ -1308,12 +1345,17 @@ bool nsXULWindow::LoadMiscPersistentAttributesFromXUL() This code does have a scary double loop -- it'll keep passing through the entire list of open windows until it finds a non-collision. Doesn't seem to be a problem, but it deserves watching. + The aRequested{X,Y} parameters here are in desktop pixels; + the aSpec{Width,Height} parameters are CSS pixel dimensions. */ void nsXULWindow::StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY, int32_t aSpecWidth, int32_t aSpecHeight) { - const int32_t kOffset = 22; - const uint32_t kSlop = 4; + // These "constants" will be converted from CSS to desktop pixels + // for the appropriate screen, assuming we find a screen to use... + // hence they're not actually declared const here. + int32_t kOffset = 22; + uint32_t kSlop = 4; bool keepTrying; int bouncedX = 0, // bounced off vertical edge of screen @@ -1354,6 +1396,17 @@ void nsXULWindow::StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY, &screenWidth, &screenHeight); screenBottom = screenTop + screenHeight; screenRight = screenLeft + screenWidth; + // Get the screen's scaling factors and convert staggering constants + // from CSS px to desktop pixel units + double desktopToDeviceScale = 1.0, cssToDeviceScale = 1.0; + ourScreen->GetContentsScaleFactor(&desktopToDeviceScale); + ourScreen->GetDefaultCSSScaleFactor(&cssToDeviceScale); + double cssToDesktopFactor = cssToDeviceScale / desktopToDeviceScale; + kOffset = NSToIntRound(kOffset * cssToDesktopFactor); + kSlop = NSToIntRound(kSlop * cssToDesktopFactor); + // Convert dimensions from CSS to desktop pixels + aSpecWidth = NSToIntRound(aSpecWidth * cssToDesktopFactor); + aSpecHeight = NSToIntRound(aSpecHeight * cssToDesktopFactor); gotScreen = true; } } @@ -1384,7 +1437,7 @@ void nsXULWindow::StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY, nsCOMPtr listBaseWindow(do_QueryInterface(supportsWindow)); listBaseWindow->GetPosition(&listX, &listY); double scale; - if (NS_SUCCEEDED(listBaseWindow->GetUnscaledDevicePixelsPerCSSPixel(&scale))) { + if (NS_SUCCEEDED(listBaseWindow->GetDevicePixelsPerDesktopPixel(&scale))) { listX = NSToIntRound(listX / scale); listY = NSToIntRound(listY / scale); } diff --git a/xpfe/appshell/nsXULWindow.h b/xpfe/appshell/nsXULWindow.h index d8f7344fab..e322b56417 100644 --- a/xpfe/appshell/nsXULWindow.h +++ b/xpfe/appshell/nsXULWindow.h @@ -93,8 +93,9 @@ protected: void OnChromeLoaded(); void StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY, int32_t aSpecWidth, int32_t aSpecHeight); - bool LoadPositionFromXUL(); - bool LoadSizeFromXUL(); + bool LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight); + bool LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight); + void SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight); bool LoadMiscPersistentAttributesFromXUL(); void SyncAttributesToWidget(); NS_IMETHOD SavePersistentAttributes();