diff --git a/accessible/ipc/ProxyAccessibleBase.cpp b/accessible/ipc/ProxyAccessibleBase.cpp index 8f1a2b7eda2e..6a6c33749b75 100644 --- a/accessible/ipc/ProxyAccessibleBase.cpp +++ b/accessible/ipc/ProxyAccessibleBase.cpp @@ -76,9 +76,7 @@ ProxyAccessibleBase::ClearChildDoc(DocAccessibleParent* aChildDoc) // in SetChildDoc(). This could result in two subsequent calls to // ClearChildDoc() even though mChildren.Length() == 1. MOZ_ASSERT(mChildren.Length() <= 1); - if (mChildren.RemoveElement(aChildDoc)) { - mOuterDoc = false; - } + mChildren.RemoveElement(aChildDoc); } template diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml index 5c61b59a580c..b0e0d71c11af 100644 --- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -3526,6 +3526,9 @@ AUa47POQ1dN5 + + RnQ3dg5KdDZs0nyFZk4= + BAAAAAABK84ykc0= @@ -3571,6 +3574,9 @@ AJiWmg== + + TA5iEg== + AN9bfYOvlR1t @@ -3811,6 +3817,9 @@ BGU= + + Hwexgn/ZCJicZPcsIyI8zxQ= + Fw== @@ -3946,6 +3955,9 @@ BAAAAAABHkSl6Co= + + Iqpyf/YoGgvHc8HiDAxAI8o= + MABJTA== @@ -4390,6 +4402,9 @@ AjqK + + RnQ3dYovwvB0D5q2YGY= + D/wZ7+m1Mv8SONSEFcs73w== @@ -4471,6 +4486,9 @@ ESDDtMgFFiaUfKo7HD9qImM7 + + BydSYg== + Aw== diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index 842ef1173b5c..ad88ce6b8a24 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -92,9 +92,6 @@ [] - - null - [] @@ -4926,28 +4923,7 @@ e10srollout@mozilla.org - 1.7 + 1.8 2 true true diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index a1776ef755ff..aad137954486 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -293,18 +293,9 @@ nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel, if (loadInfo) { if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) { - RefPtr prin; - if (loadInfo->LoadingPrincipal()) { - prin = - nsNullPrincipal::CreateWithInheritedAttributes(loadInfo->LoadingPrincipal()); - } else { - OriginAttributes attrs; - loadInfo->GetOriginAttributes(&attrs); - attrs.StripAttributes(OriginAttributes::STRIP_ADDON_ID); - prin = nsNullPrincipal::Create(attrs); - } - prin.forget(aPrincipal); - return NS_OK; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(loadInfo->GetSandboxedLoadingPrincipal(aPrincipal))); + MOZ_ASSERT(*aPrincipal); + return NS_OK; } bool forceInherit = loadInfo->GetForceInheritPrincipal(); diff --git a/config/rules.mk b/config/rules.mk index e5007d8b3c2b..f896333aa3cd 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -943,7 +943,13 @@ endif # optimization level here, if necessary. (The Cargo.toml files already # specify debug-assertions appropriately for --{disable,enable}-debug.) ifndef MOZ_OPTIMIZE -rustflags_override = RUSTFLAGS='-C opt-level=0' +rustflags = -C opt-level=0 +# Unfortunately, -C opt-level=0 implies -C debug-assertions, so we need +# to explicitly disable them when MOZ_DEBUG is not set. +ifndef MOZ_DEBUG +rustflags += -C debug-assertions=no +endif +rustflags_override = RUSTFLAGS='$(rustflags)' endif CARGO_BUILD = env $(rustflags_override) \ diff --git a/devtools/client/inspector/layout/components/BoxModelMain.js b/devtools/client/inspector/layout/components/BoxModelMain.js index 34319bcd0711..42baa7427453 100644 --- a/devtools/client/inspector/layout/components/BoxModelMain.js +++ b/devtools/client/inspector/layout/components/BoxModelMain.js @@ -157,7 +157,7 @@ module.exports = createClass({ title: BOXMODEL_L10N.getStr("boxmodel.content"), }) ) - ), + ) ), BoxModelEditable({ box: "margin", diff --git a/devtools/client/inspector/webpack.config.js b/devtools/client/inspector/webpack.config.js index 5d8967265d68..574a9ad15236 100644 --- a/devtools/client/inspector/webpack.config.js +++ b/devtools/client/inspector/webpack.config.js @@ -94,7 +94,8 @@ module.exports = envConfig => { "AppConstants": "{ DEBUG: true, DEBUG_JS_MODULES: true }", "loader": `{ lazyRequireGetter: () => {}, - lazyGetter: () => {} + lazyGetter: () => {}, + lazyImporter: () => {} }`, "dump": "console.log", }) diff --git a/devtools/client/shared/components/tabs/tabs.js b/devtools/client/shared/components/tabs/tabs.js index f48ffc1aa457..86bea3752609 100644 --- a/devtools/client/shared/components/tabs/tabs.js +++ b/devtools/client/shared/components/tabs/tabs.js @@ -349,7 +349,7 @@ define(function (require, exports, module) { return ( DOM.div({ className: ["tabs", this.props.className].join(" ") }, this.renderMenuItems(), - this.renderPanels(), + this.renderPanels() ) ); }, diff --git a/dom/base/IdleRequest.cpp b/dom/base/IdleRequest.cpp index 904e7d2cc769..99a5af7c0627 100644 --- a/dom/base/IdleRequest.cpp +++ b/dom/base/IdleRequest.cpp @@ -8,7 +8,6 @@ #include "mozilla/TimeStamp.h" #include "mozilla/dom/IdleDeadline.h" -#include "mozilla/dom/Performance.h" #include "mozilla/dom/PerformanceTiming.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/dom/WindowBinding.h" @@ -20,138 +19,56 @@ namespace mozilla { namespace dom { -IdleRequest::IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow, - IdleRequestCallback& aCallback, uint32_t aHandle) - : mWindow(aWindow) - , mCallback(&aCallback) +IdleRequest::IdleRequest(IdleRequestCallback* aCallback, uint32_t aHandle) + : mCallback(aCallback) , mHandle(aHandle) , mTimeoutHandle(Nothing()) { - MOZ_ASSERT(aWindow); - - // Get the calling location. - nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn); + MOZ_DIAGNOSTIC_ASSERT(mCallback); } IdleRequest::~IdleRequest() { } -NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest) +NS_IMPL_CYCLE_COLLECTION(IdleRequest, mCallback) NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequest) -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequest) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequest) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequest) - NS_INTERFACE_MAP_ENTRY(nsIRunnable) - NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable) - NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable) - NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimeoutHandler) NS_INTERFACE_MAP_END -nsresult -IdleRequest::SetTimeout(uint32_t aTimeout) -{ - int32_t handle; - nsresult rv = mWindow->TimeoutManager().SetTimeout( - this, aTimeout, false, Timeout::Reason::eIdleCallbackTimeout, &handle); - mTimeoutHandle = Some(handle); - - return rv; -} - -nsresult -IdleRequest::Run() -{ - if (mCallback) { - RunIdleRequestCallback(false); - } - - return NS_OK; -} - -nsresult -IdleRequest::Cancel() -{ - mCallback = nullptr; - CancelTimeout(); - if (isInList()) { - remove(); - } - Release(); - - return NS_OK; -} - void -IdleRequest::SetDeadline(TimeStamp aDeadline) +IdleRequest::SetTimeoutHandle(int32_t aHandle) { - mozilla::dom::Performance* perf = mWindow->GetPerformance(); - mDeadline = - perf ? perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline) : 0.0; + mTimeoutHandle = Some(aHandle); +} + +uint32_t +IdleRequest::GetTimeoutHandle() const +{ + MOZ_DIAGNOSTIC_ASSERT(mTimeoutHandle.isSome()); + return mTimeoutHandle.value(); } nsresult -IdleRequest::RunIdleRequestCallback(bool aDidTimeout) +IdleRequest::IdleRun(nsPIDOMWindowInner* aWindow, + DOMHighResTimeStamp aDeadline, + bool aDidTimeout) { MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(mCallback); - if (!aDidTimeout) { - CancelTimeout(); - } - - remove(); ErrorResult error; RefPtr deadline = - new IdleDeadline(mWindow, aDidTimeout, mDeadline); + new IdleDeadline(aWindow, aDidTimeout, aDeadline); mCallback->Call(*deadline, error, "requestIdleCallback handler"); + mCallback = nullptr; - Release(); - + error.SuppressException(); return error.StealNSResult(); } -void -IdleRequest::CancelTimeout() -{ - if (mTimeoutHandle.isSome()) { - mWindow->TimeoutManager().ClearTimeout( - mTimeoutHandle.value(), Timeout::Reason::eIdleCallbackTimeout); - } -} - -nsresult -IdleRequest::Call() -{ - SetDeadline(TimeStamp::Now()); - return RunIdleRequestCallback(true); -} - -void -IdleRequest::GetLocation(const char** aFileName, uint32_t* aLineNo, - uint32_t* aColumn) -{ - *aFileName = mFileName.get(); - *aLineNo = mLineNo; - *aColumn = mColumn; -} - -void -IdleRequest::MarkForCC() -{ - mCallback->MarkForCC(); -} - } // namespace dom } // namespace mozilla diff --git a/dom/base/IdleRequest.h b/dom/base/IdleRequest.h index d8a6a2fad9bf..40f910bf286c 100644 --- a/dom/base/IdleRequest.h +++ b/dom/base/IdleRequest.h @@ -15,7 +15,6 @@ #include "nsICancelableRunnable.h" #include "nsIIncrementalRunnable.h" #include "nsIRunnable.h" -#include "nsITimeoutHandler.h" #include "nsString.h" class nsPIDOMWindowInner; @@ -25,28 +24,19 @@ namespace dom { class IdleRequestCallback; -class IdleRequest final : public nsITimeoutHandler - , public nsIRunnable - , public nsICancelableRunnable - , public nsIIncrementalRunnable - , public LinkedListElement +class IdleRequest final : public nsISupports, + public LinkedListElement { public: - IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow, - IdleRequestCallback& aCallback, uint32_t aHandle); + IdleRequest(IdleRequestCallback* aCallback, uint32_t aHandle); - virtual nsresult Call() override; - virtual void GetLocation(const char** aFileName, uint32_t* aLineNo, - uint32_t* aColumn) override; - virtual void MarkForCC() override; + nsresult IdleRun(nsPIDOMWindowInner* aWindow, + DOMHighResTimeStamp aDeadline, + bool aDidTimeout); - nsresult SetTimeout(uint32_t aTimout); - nsresult RunIdleRequestCallback(bool aDidTimeout); - void CancelTimeout(); - - NS_DECL_NSIRUNNABLE; - virtual nsresult Cancel() override; - virtual void SetDeadline(mozilla::TimeStamp aDeadline) override; + void SetTimeoutHandle(int32_t aHandle); + bool HasTimeout() const { return mTimeoutHandle.isSome(); } + uint32_t GetTimeoutHandle() const; uint32_t Handle() const { @@ -54,22 +44,14 @@ public: } NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequest, nsITimeoutHandler) + NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequest) private: ~IdleRequest(); - // filename, line number and JS language version string of the - // caller of setTimeout() - nsCString mFileName; - uint32_t mLineNo; - uint32_t mColumn; - - nsCOMPtr mWindow; RefPtr mCallback; - uint32_t mHandle; + const uint32_t mHandle; mozilla::Maybe mTimeoutHandle; - DOMHighResTimeStamp mDeadline; }; } // namespace dom diff --git a/dom/base/TabGroup.cpp b/dom/base/TabGroup.cpp index fde8dfe24da0..254921d83473 100644 --- a/dom/base/TabGroup.cpp +++ b/dom/base/TabGroup.cpp @@ -26,6 +26,7 @@ static StaticRefPtr sChromeTabGroup; TabGroup::TabGroup(bool aIsChrome) : mLastWindowLeft(false) , mThrottledQueuesInitialized(false) + , mIsChrome(aIsChrome) { for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { TaskCategory category = static_cast(i); @@ -59,7 +60,7 @@ TabGroup::~TabGroup() { MOZ_ASSERT(mDocGroups.IsEmpty()); MOZ_ASSERT(mWindows.IsEmpty()); - MOZ_RELEASE_ASSERT(mLastWindowLeft); + MOZ_RELEASE_ASSERT(mLastWindowLeft || mIsChrome); } void @@ -173,7 +174,7 @@ TabGroup::Leave(nsPIDOMWindowOuter* aWindow) // The Chrome TabGroup doesn't have cyclical references through mEventTargets // to itself, meaning that we don't have to worry about nulling mEventTargets // out after the last window leaves. - if (sChromeTabGroup != this && mWindows.IsEmpty()) { + if (!mIsChrome && mWindows.IsEmpty()) { mLastWindowLeft = true; // There is a RefPtr cycle TabGroup -> DispatcherEventTarget -> TabGroup. To @@ -270,7 +271,7 @@ TabGroup::EventTargetFor(TaskCategory aCategory) const { MOZ_ASSERT(aCategory != TaskCategory::Count); if (aCategory == TaskCategory::Worker || aCategory == TaskCategory::Timer) { - MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || this == sChromeTabGroup); + MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || mIsChrome); } if (NS_WARN_IF(mLastWindowLeft)) { diff --git a/dom/base/TabGroup.h b/dom/base/TabGroup.h index 2ba59a8d9f48..828e8e255075 100644 --- a/dom/base/TabGroup.h +++ b/dom/base/TabGroup.h @@ -129,10 +129,15 @@ private: void EnsureThrottledEventQueues(); ~TabGroup(); - DocGroupMap mDocGroups; + + // Thread-safe members Atomic mLastWindowLeft; - nsTArray mWindows; Atomic mThrottledQueuesInitialized; + const bool mIsChrome; + + // Main thread only + DocGroupMap mDocGroups; + nsTArray mWindows; nsCOMPtr mEventTargets[size_t(TaskCategory::Count)]; RefPtr mAbstractThreads[size_t(TaskCategory::Count)]; }; diff --git a/dom/base/Timeout.h b/dom/base/Timeout.h index 27e15da4ae7c..a5206162aeb9 100644 --- a/dom/base/Timeout.h +++ b/dom/base/Timeout.h @@ -41,7 +41,11 @@ public: // default main thread being used. nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay); - enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout }; + enum class Reason + { + eTimeoutOrInterval, + eIdleCallbackTimeout, + }; #ifdef DEBUG bool HasRefCnt(uint32_t aCount) const; @@ -76,6 +80,8 @@ public: // True if this is a timeout coming from a tracking script bool mIsTracking; + // Used to allow several reasons for setting a timeout, where each + // 'Reason' value is using a possibly overlapping set of id:s. Reason mReason; // Returned as value of setTimeout() diff --git a/dom/base/TimeoutHandler.cpp b/dom/base/TimeoutHandler.cpp new file mode 100644 index 000000000000..78c3f16dd95d --- /dev/null +++ b/dom/base/TimeoutHandler.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TimeoutHandler.h" + +namespace mozilla { +namespace dom { + +TimeoutHandler::TimeoutHandler(JSContext* aCx) + : TimeoutHandler() +{ + nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn); +} + +nsresult +TimeoutHandler::Call() +{ + return NS_OK; +} + +void +TimeoutHandler::GetLocation(const char** aFileName, uint32_t* aLineNo, + uint32_t* aColumn) +{ + *aFileName = mFileName.get(); + *aLineNo = mLineNo; + *aColumn = mColumn; +} + +NS_IMPL_CYCLE_COLLECTION_0(TimeoutHandler) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TimeoutHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TimeoutHandler) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) +NS_INTERFACE_MAP_END + +} // namespace dom +} // namespace mozilla diff --git a/dom/base/TimeoutHandler.h b/dom/base/TimeoutHandler.h new file mode 100644 index 000000000000..cb0a0ce945e5 --- /dev/null +++ b/dom/base/TimeoutHandler.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_timeout_handler_h +#define mozilla_dom_timeout_handler_h + +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsITimeoutHandler.h" + +namespace mozilla { +namespace dom { + +/** + * Utility class for implementing nsITimeoutHandlers, designed to be subclassed. + */ +class TimeoutHandler : public nsITimeoutHandler +{ +public: + // TimeoutHandler doesn't actually contain cycles, but subclasses + // probably will. + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(TimeoutHandler) + + virtual nsresult Call() override; + virtual void GetLocation(const char** aFileName, uint32_t* aLineNo, + uint32_t* aColumn) override; + virtual void MarkForCC() override {} +protected: + TimeoutHandler() : mFileName(""), mLineNo(0), mColumn(0) {} + explicit TimeoutHandler(JSContext *aCx); + + virtual ~TimeoutHandler() {} +private: + TimeoutHandler(const TimeoutHandler&) = delete; + TimeoutHandler& operator=(const TimeoutHandler&) = delete; + TimeoutHandler& operator=(const TimeoutHandler&&) = delete; + + nsCString mFileName; + uint32_t mLineNo; + uint32_t mColumn; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_timeout_handler_h diff --git a/dom/base/moz.build b/dom/base/moz.build index 025200c48a4d..4e8c3b347083 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -205,6 +205,7 @@ EXPORTS.mozilla.dom += [ 'TabGroup.h', 'Text.h', 'Timeout.h', + 'TimeoutHandler.h', 'TimeoutManager.h', 'TreeWalker.h', 'WebKitCSSMatrix.h', @@ -345,6 +346,7 @@ UNIFIED_SOURCES += [ 'TextInputProcessor.cpp', 'ThirdPartyUtil.cpp', 'Timeout.cpp', + 'TimeoutHandler.cpp', 'TimeoutManager.cpp', 'TreeWalker.cpp', 'WebKitCSSMatrix.cpp', diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index aec242869314..797b00f801b0 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -6176,7 +6176,9 @@ nsresult nsContentTypeParser::GetType(nsAString& aResult) const { nsresult rv = GetParameter(nullptr, aResult); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + return rv; + } nsContentUtils::ASCIIToLower(aResult); return NS_OK; } diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 4fcbc8525534..c24818ad2261 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -23,6 +23,7 @@ #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEventBinding.h" #include "mozilla/dom/Timeout.h" +#include "mozilla/dom/TimeoutHandler.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/IntegerPrintfMacros.h" #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) @@ -522,29 +523,266 @@ DialogValueHolder::Get(JSContext* aCx, JS::Handle aScope, } } -void -nsGlobalWindow::PostThrottledIdleCallback() +class IdleRequestExecutor final : public nsIRunnable + , public nsICancelableRunnable + , public nsIIncrementalRunnable { - AssertIsOnMainThread(); +public: + explicit IdleRequestExecutor(nsGlobalWindow* aWindow) + : mDispatched(false) + , mDeadline(TimeStamp::Now()) + , mWindow(aWindow) + { + MOZ_DIAGNOSTIC_ASSERT(mWindow); + MOZ_DIAGNOSTIC_ASSERT(mWindow->IsInnerWindow()); + } - if (mThrottledIdleRequestCallbacks.isEmpty()) + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable) + + NS_DECL_NSIRUNNABLE + nsresult Cancel() override; + void SetDeadline(TimeStamp aDeadline) override; + + void MaybeDispatch(); +private: + ~IdleRequestExecutor() {} + + bool mDispatched; + TimeStamp mDeadline; + RefPtr mWindow; +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequestExecutor) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor) +NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequestExecutor) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequestExecutor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor) + NS_INTERFACE_MAP_ENTRY(nsIRunnable) + NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable) + NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +IdleRequestExecutor::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mDispatched = false; + if (mWindow) { + return mWindow->ExecuteIdleRequest(mDeadline); + } + + return NS_OK; +} + +nsresult +IdleRequestExecutor::Cancel() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mWindow = nullptr; + return NS_OK; +} + +void +IdleRequestExecutor::SetDeadline(TimeStamp aDeadline) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mWindow) { return; + } - RefPtr request(mThrottledIdleRequestCallbacks.popFirst()); - // ownership transferred from mThrottledIdleRequestCallbacks to - // mIdleRequestCallbacks - mIdleRequestCallbacks.insertBack(request); + mDeadline = aDeadline; +} + +void +IdleRequestExecutor::MaybeDispatch() +{ + MOZ_DIAGNOSTIC_ASSERT(mWindow); + + if (mDispatched) { + return; + } + + mDispatched = true; + RefPtr request = this; NS_IdleDispatchToCurrentThread(request.forget()); } -/* static */ void -nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest, - IdleRequests& aList) +class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler { - aList.insertBack(aRequest); +public: + explicit IdleRequestExecutorTimeoutHandler(IdleRequestExecutor* aExecutor) + : mExecutor(aExecutor) + { + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestExecutorTimeoutHandler, + TimeoutHandler) + + nsresult Call() override + { + mExecutor->MaybeDispatch(); + return NS_OK; + } +private: + ~IdleRequestExecutorTimeoutHandler() {} + RefPtr mExecutor; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler, mExecutor) + +NS_IMPL_ADDREF_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler) +NS_IMPL_RELEASE_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) +NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler) + +void +nsGlobalWindow::ScheduleIdleRequestDispatch() +{ + AssertIsOnMainThread(); + + if (mIdleRequestCallbacks.isEmpty()) { + if (mIdleRequestExecutor) { + mIdleRequestExecutor->Cancel(); + mIdleRequestExecutor = nullptr; + } + + return; + } + + if (!mIdleRequestExecutor) { + mIdleRequestExecutor = new IdleRequestExecutor(this); + } + + nsPIDOMWindowOuter* outer = GetOuterWindow(); + if (outer && outer->AsOuter()->IsBackground()) { + nsCOMPtr handler = new IdleRequestExecutorTimeoutHandler(mIdleRequestExecutor); + int32_t dummy; + // Set a timeout handler with a timeout of 0 ms to throttle idle + // callback requests coming from a backround window using + // background timeout throttling. + mTimeoutManager->SetTimeout(handler, 0, false, + Timeout::Reason::eIdleCallbackTimeout, &dummy); + return; + } + + mIdleRequestExecutor->MaybeDispatch(); +} + +void +nsGlobalWindow::InsertIdleCallback(IdleRequest* aRequest) +{ + AssertIsOnMainThread(); + mIdleRequestCallbacks.insertBack(aRequest); aRequest->AddRef(); } +void +nsGlobalWindow::RemoveIdleCallback(mozilla::dom::IdleRequest* aRequest) +{ + AssertIsOnMainThread(); + + if (aRequest->HasTimeout()) { + mTimeoutManager->ClearTimeout(aRequest->GetTimeoutHandle(), + Timeout::Reason::eIdleCallbackTimeout); + } + + aRequest->removeFrom(mIdleRequestCallbacks); + aRequest->Release(); +} + +nsresult +nsGlobalWindow::RunIdleRequest(IdleRequest* aRequest, + DOMHighResTimeStamp aDeadline, + bool aDidTimeout) +{ + AssertIsOnMainThread(); + RefPtr request(aRequest); + nsresult result = request->IdleRun(AsInner(), aDeadline, aDidTimeout); + RemoveIdleCallback(request); + return result; +} + +nsresult +nsGlobalWindow::ExecuteIdleRequest(TimeStamp aDeadline) +{ + AssertIsOnMainThread(); + RefPtr request = mIdleRequestCallbacks.getFirst(); + + if (!request) { + // There are no more idle requests, so stop scheduling idle + // request callbacks. + return NS_OK; + } + + DOMHighResTimeStamp deadline = 0.0; + + if (Performance* perf = GetPerformance()) { + deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline); + } + + nsresult result = RunIdleRequest(request, deadline, false); + + ScheduleIdleRequestDispatch(); + return result; +} + +class IdleRequestTimeoutHandler final : public TimeoutHandler +{ +public: + IdleRequestTimeoutHandler(JSContext* aCx, + IdleRequest* aIdleRequest, + nsPIDOMWindowInner* aWindow) + : TimeoutHandler(aCx) + , mIdleRequest(aIdleRequest) + , mWindow(aWindow) + { + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestTimeoutHandler, + TimeoutHandler) + + nsresult Call() override + { + return nsGlobalWindow::Cast(mWindow)->RunIdleRequest(mIdleRequest, 0.0, true); + } + +private: + ~IdleRequestTimeoutHandler() {} + + RefPtr mIdleRequest; + nsCOMPtr mWindow; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestTimeoutHandler, + TimeoutHandler, + mIdleRequest, + mWindow) + +NS_IMPL_ADDREF_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler) +NS_IMPL_RELEASE_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) +NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler) + uint32_t nsGlobalWindow::RequestIdleCallback(JSContext* aCx, IdleRequestCallback& aCallback, @@ -554,33 +792,35 @@ nsGlobalWindow::RequestIdleCallback(JSContext* aCx, MOZ_RELEASE_ASSERT(IsInnerWindow()); AssertIsOnMainThread(); - uint32_t handle = ++mIdleRequestCallbackCounter; + uint32_t handle = mIdleRequestCallbackCounter++; RefPtr request = - new IdleRequest(aCx, AsInner(), aCallback, handle); + new IdleRequest(&aCallback, handle); if (aOptions.mTimeout.WasPassed()) { - aError = request->SetTimeout(aOptions.mTimeout.Value()); - if (NS_WARN_IF(aError.Failed())) { + int32_t timeoutHandle; + nsCOMPtr handler(new IdleRequestTimeoutHandler(aCx, request, AsInner())); + + nsresult rv = mTimeoutManager->SetTimeout( + handler, aOptions.mTimeout.Value(), false, + Timeout::Reason::eIdleCallbackTimeout, &timeoutHandle); + + if (NS_WARN_IF(NS_FAILED(rv))) { return 0; } + + request->SetTimeoutHandle(timeoutHandle); } - nsGlobalWindow* outer = GetOuterWindowInternal(); - if (outer && outer->AsOuter()->IsBackground()) { - // mThrottledIdleRequestCallbacks now owns request - InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks); + // If the list of idle callback requests is not empty it means that + // we've already dispatched the first idle request. It is the + // responsibility of that to dispatch the next. + bool needsScheduling = mIdleRequestCallbacks.isEmpty(); + // mIdleRequestCallbacks now owns request + InsertIdleCallback(request); - NS_DelayedDispatchToCurrentThread( - NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback), - 10000); - } else { - MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty()); - - // mIdleRequestCallbacks now owns request - InsertIdleCallbackIntoList(request, mIdleRequestCallbacks); - - NS_IdleDispatchToCurrentThread(request.forget()); + if (needsScheduling) { + ScheduleIdleRequestDispatch(); } return handle; @@ -593,7 +833,7 @@ nsGlobalWindow::CancelIdleCallback(uint32_t aHandle) for (IdleRequest* r : mIdleRequestCallbacks) { if (r->Handle() == aHandle) { - r->Cancel(); + RemoveIdleCallback(r); break; } } @@ -602,25 +842,14 @@ nsGlobalWindow::CancelIdleCallback(uint32_t aHandle) void nsGlobalWindow::DisableIdleCallbackRequests() { + if (mIdleRequestExecutor) { + mIdleRequestExecutor->Cancel(); + mIdleRequestExecutor = nullptr; + } + while (!mIdleRequestCallbacks.isEmpty()) { - RefPtr request = mIdleRequestCallbacks.popFirst(); - request->Cancel(); - } - - while (!mThrottledIdleRequestCallbacks.isEmpty()) { - RefPtr request = mThrottledIdleRequestCallbacks.popFirst(); - request->Cancel(); - } -} - -void nsGlobalWindow::UnthrottleIdleCallbackRequests() -{ - AssertIsOnMainThread(); - - while (!mThrottledIdleRequestCallbacks.isEmpty()) { - RefPtr request(mThrottledIdleRequestCallbacks.popFirst()); - mIdleRequestCallbacks.insertBack(request); - NS_IdleDispatchToCurrentThread(request.forget()); + RefPtr request = mIdleRequestCallbacks.getFirst(); + RemoveIdleCallback(request); } } @@ -629,7 +858,6 @@ nsGlobalWindow::IsBackgroundInternal() const { return !mOuterWindow || mOuterWindow->IsBackground(); } - namespace mozilla { namespace dom { extern uint64_t @@ -1220,6 +1448,7 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) mFocusMethod(0), mSerial(0), mIdleRequestCallbackCounter(1), + mIdleRequestExecutor(nullptr), #ifdef DEBUG mSetOpenerWindowCalled(false), #endif @@ -1931,14 +2160,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor) for (IdleRequest* request : tmp->mIdleRequestCallbacks) { cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); } - for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) { - cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); - } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads) @@ -2043,6 +2269,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) tmp->UnlinkHostObjectURIs(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor) tmp->DisableIdleCallbackRequests(); NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER @@ -2981,7 +3208,11 @@ nsGlobalWindow::PreloadLocalStorage() // private browsing windows do not persist local storage to disk so we should // only try to precache storage when we're not a private browsing window. if (principal->GetPrivateBrowsingId() == 0) { - storageManager->PrecacheStorage(principal); + nsCOMPtr storage; + rv = storageManager->PrecacheStorage(principal, getter_AddRefs(storage)); + if (NS_SUCCEEDED(rv)) { + mLocalStorage = static_cast(storage.get()); + } } } @@ -10042,7 +10273,6 @@ void nsGlobalWindow::SetIsBackground(bool aIsBackground) inner->mTimeoutManager->ResetTimersForThrottleReduction(); } - inner->UnthrottleIdleCallbackRequests(); inner->SyncGamepadState(); } @@ -11563,48 +11793,43 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic, } bool isPrivateBrowsing = IsPrivateBrowsing(); + // In addition to determining if this is a storage event, the + // isPrivateBrowsing checks here enforce that the source storage area's + // private browsing state matches this window's state. These flag checks + // and their maintenance independent from the principal's OriginAttributes + // matter because chrome docshells that are part of private browsing windows + // can be private browsing without having their OriginAttributes set (because + // they have the system principal). if ((!nsCRT::strcmp(aTopic, "dom-storage2-changed") && !isPrivateBrowsing) || (!nsCRT::strcmp(aTopic, "dom-private-storage2-changed") && isPrivateBrowsing)) { if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) { return NS_OK; } - nsIPrincipal *principal; - nsresult rv; + nsIPrincipal *principal = GetPrincipal(); + if (!principal) { + return NS_OK; + } RefPtr event = static_cast(aSubject); if (!event) { return NS_ERROR_FAILURE; } - RefPtr changingStorage = event->GetStorageArea(); - if (!changingStorage) { - return NS_ERROR_FAILURE; - } - - nsCOMPtr istorage = changingStorage.get(); - bool fireMozStorageChanged = false; nsAutoString eventType; eventType.AssignLiteral("storage"); - principal = GetPrincipal(); - if (!principal) { - return NS_OK; - } - if (changingStorage->IsPrivate() != IsPrivateBrowsing()) { - return NS_OK; - } + if (!NS_strcmp(aData, u"sessionStorage")) { + nsCOMPtr changingStorage = event->GetStorageArea(); + MOZ_ASSERT(changingStorage); - switch (changingStorage->GetType()) - { - case Storage::SessionStorage: - { bool check = false; nsCOMPtr storageManager = do_QueryInterface(GetDocShell()); if (storageManager) { - rv = storageManager->CheckStorage(principal, istorage, &check); + nsresult rv = storageManager->CheckStorage(principal, changingStorage, + &check); if (NS_FAILED(rv)) { return rv; } @@ -11625,44 +11850,43 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic, if (fireMozStorageChanged) { eventType.AssignLiteral("MozSessionStorageChanged"); } - break; } - case Storage::LocalStorage: - { - // Allow event fire only for the same principal storages - // XXX We have to use EqualsIgnoreDomain after bug 495337 lands - nsIPrincipal* storagePrincipal = changingStorage->GetPrincipal(); + else { + MOZ_ASSERT(!NS_strcmp(aData, u"localStorage")); + nsIPrincipal* storagePrincipal = event->GetPrincipal(); + if (!storagePrincipal) { + return NS_OK; + } bool equals = false; - rv = storagePrincipal->Equals(principal, &equals); + nsresult rv = storagePrincipal->Equals(principal, &equals); NS_ENSURE_SUCCESS(rv, rv); - if (!equals) + if (!equals) { return NS_OK; + } + + fireMozStorageChanged = mLocalStorage == event->GetStorageArea(); - fireMozStorageChanged = mLocalStorage == changingStorage; if (fireMozStorageChanged) { eventType.AssignLiteral("MozLocalStorageChanged"); } - break; - } - default: - return NS_OK; } // Clone the storage event included in the observer notification. We want // to dispatch clones rather than the original event. ErrorResult error; - RefPtr newEvent = CloneStorageEvent(eventType, event, error); + RefPtr clonedEvent = + CloneStorageEvent(eventType, event, error); if (error.Failed()) { return error.StealNSResult(); } - newEvent->SetTrusted(true); + clonedEvent->SetTrusted(true); if (fireMozStorageChanged) { - WidgetEvent* internalEvent = newEvent->WidgetEventPtr(); + WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr(); internalEvent->mFlags.mOnlyChromeDispatch = true; } @@ -11671,12 +11895,12 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic, // store the domain in which the change happened and fire the // events if we're ever thawed. - mPendingStorageEvents.AppendElement(newEvent); + mPendingStorageEvents.AppendElement(clonedEvent); return NS_OK; } bool defaultActionEnabled; - DispatchEvent(newEvent, &defaultActionEnabled); + DispatchEvent(clonedEvent, &defaultActionEnabled); return NS_OK; } @@ -11767,10 +11991,19 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType, aEvent->GetUrl(dict.mUrl); RefPtr storageArea = aEvent->GetStorageArea(); - MOZ_ASSERT(storageArea); RefPtr storage; - if (storageArea->GetType() == Storage::LocalStorage) { + + // If null, this is a localStorage event received by IPC. + if (!storageArea) { + storage = GetLocalStorage(aRv); + if (aRv.Failed() || !storage) { + return nullptr; + } + + // We must apply the current change to the 'local' localStorage. + storage->ApplyEvent(aEvent); + } else if (storageArea->GetType() == Storage::LocalStorage) { storage = GetLocalStorage(aRv); } else { MOZ_ASSERT(storageArea->GetType() == Storage::SessionStorage); @@ -11782,7 +12015,7 @@ nsGlobalWindow::CloneStorageEvent(const nsAString& aType, } MOZ_ASSERT(storage); - MOZ_ASSERT(storage->IsForkOf(storageArea)); + MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea)); dict.mStorageArea = storage; @@ -12943,6 +13176,13 @@ nsGlobalWindow::EventListenerAdded(nsIAtom* aType) aType == nsGkAtoms::onvrdisplaypresentchange) { NotifyVREventListenerAdded(); } + + // We need to initialize localStorage in order to receive notifications. + if (aType == nsGkAtoms::onstorage) { + ErrorResult rv; + GetLocalStorage(rv); + rv.SuppressException(); + } } void diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 49eee47426d3..e08344138880 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -102,6 +102,8 @@ struct nsRect; class nsWindowSizes; +class IdleRequestExecutor; + namespace mozilla { class AbstractThread; class DOMEventTargetHelper; @@ -119,6 +121,7 @@ class Gamepad; enum class ImageBitmapFormat : uint8_t; class IdleRequest; class IdleRequestCallback; +class IncrementalRunnable; class Location; class MediaQueryList; class MozSelfSupport; @@ -1106,7 +1109,6 @@ public: mozilla::ErrorResult& aError); void CancelIdleCallback(uint32_t aHandle); - #ifdef MOZ_WEBSPEECH mozilla::dom::SpeechSynthesis* GetSpeechSynthesis(mozilla::ErrorResult& aError); @@ -1786,6 +1788,18 @@ public: virtual mozilla::AbstractThread* AbstractMainThreadFor(mozilla::TaskCategory aCategory) override; + void DisableIdleCallbackRequests(); + uint32_t IdleRequestHandle() const { return mIdleRequestCallbackCounter; } + nsresult RunIdleRequest(mozilla::dom::IdleRequest* aRequest, + DOMHighResTimeStamp aDeadline, bool aDidTimeout); + nsresult ExecuteIdleRequest(TimeStamp aDeadline); + void ScheduleIdleRequestDispatch(); + + typedef mozilla::LinkedList IdleRequests; + void InsertIdleCallback(mozilla::dom::IdleRequest* aRequest); + + void RemoveIdleCallback(mozilla::dom::IdleRequest* aRequest); + protected: // These members are only used on outer window objects. Make sure // you never set any of these on an inner object! @@ -1923,19 +1937,10 @@ protected: uint32_t mSerial; - void DisableIdleCallbackRequests(); - void UnthrottleIdleCallbackRequests(); - - void PostThrottledIdleCallback(); - - typedef mozilla::LinkedList IdleRequests; - static void InsertIdleCallbackIntoList(mozilla::dom::IdleRequest* aRequest, - IdleRequests& aList); - // The current idle request callback handle uint32_t mIdleRequestCallbackCounter; IdleRequests mIdleRequestCallbacks; - IdleRequests mThrottledIdleRequestCallbacks; + RefPtr mIdleRequestExecutor; #ifdef DEBUG bool mSetOpenerWindowCalled; @@ -2012,6 +2017,7 @@ protected: friend class mozilla::dom::PostMessageEvent; friend class DesktopNotification; friend class mozilla::dom::TimeoutManager; + friend class IdleRequestExecutor; static WindowByIdTable* sWindowsById; static bool sWarnedAboutWindowInternal; diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 589a1ca7514b..41a215c0bfd3 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -5346,7 +5346,6 @@ CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& aWindow, double aX, DrawSurfaceOptions(gfx::SamplingFilter::POINT), DrawOptions(GlobalAlpha(), UsedOperation(), AntialiasMode::NONE)); - mTarget->Flush(); } else { mTarget->SetTransform(matrix); } diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list index c85d03829e60..20379370ff5f 100644 --- a/dom/canvas/crashtests/crashtests.list +++ b/dom/canvas/crashtests/crashtests.list @@ -39,6 +39,6 @@ load 1286458-1.html load 1299062-1.html load 1305312-1.html load 1298576-1.html -load 1334366-1.html +asserts-if(stylo,1) load 1334366-1.html # bug 1324700 load 1334647-1.html diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index 111d74d70200..beeb5c4587d2 100644 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -270,16 +270,10 @@ Event::GetType(nsAString& aType) return NS_OK; } -static EventTarget* -GetDOMEventTarget(nsIDOMEventTarget* aTarget) -{ - return aTarget ? aTarget->GetTargetForDOMEvent() : nullptr; -} - EventTarget* Event::GetTarget() const { - return GetDOMEventTarget(mEvent->mTarget); + return mEvent->GetDOMEventTarget(); } NS_IMETHODIMP @@ -292,7 +286,7 @@ Event::GetTarget(nsIDOMEventTarget** aTarget) EventTarget* Event::GetCurrentTarget() const { - return GetDOMEventTarget(mEvent->mCurrentTarget); + return mEvent->GetCurrentDOMEventTarget(); } NS_IMETHODIMP @@ -339,11 +333,7 @@ Event::GetExplicitOriginalTarget(nsIDOMEventTarget** aRealEventTarget) EventTarget* Event::GetOriginalTarget() const { - if (mEvent->mOriginalTarget) { - return GetDOMEventTarget(mEvent->mOriginalTarget); - } - - return GetTarget(); + return mEvent->GetOriginalDOMEventTarget(); } NS_IMETHODIMP @@ -402,8 +392,17 @@ Event::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) { nsCOMPtr t = do_QueryInterface(aGlobal.GetAsSupports()); - RefPtr e = new Event(t, nullptr, nullptr); - bool trusted = e->Init(t); + return Constructor(t, aType, aParam); +} + +// static +already_AddRefed +Event::Constructor(EventTarget* aEventTarget, + const nsAString& aType, + const EventInit& aParam) +{ + RefPtr e = new Event(aEventTarget, nullptr, nullptr); + bool trusted = e->Init(aEventTarget); e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable); e->SetTrusted(trusted); e->SetComposed(aParam.mComposed); @@ -1207,7 +1206,7 @@ Event::Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) } NS_IMETHODIMP_(void) -Event::SetOwner(mozilla::dom::EventTarget* aOwner) +Event::SetOwner(EventTarget* aOwner) { mOwner = nullptr; diff --git a/dom/events/Event.h b/dom/events/Event.h index 8f8334fceda7..545bf72676ef 100644 --- a/dom/events/Event.h +++ b/dom/events/Event.h @@ -142,6 +142,10 @@ public: LayoutDeviceIntPoint aPoint, CSSIntPoint aDefaultPoint); + static already_AddRefed Constructor(EventTarget* aEventTarget, + const nsAString& aType, + const EventInit& aParam); + static already_AddRefed Constructor(const GlobalObject& aGlobal, const nsAString& aType, const EventInit& aParam, diff --git a/dom/events/StorageEvent.h b/dom/events/StorageEvent.h index e82baf34131f..f9639ef58b50 100644 --- a/dom/events/StorageEvent.h +++ b/dom/events/StorageEvent.h @@ -13,6 +13,8 @@ #include "mozilla/dom/Event.h" #include "mozilla/dom/StorageEventBinding.h" +class nsIPrincipal; + namespace mozilla { namespace dom { @@ -34,6 +36,7 @@ protected: nsString mNewValue; nsString mUrl; RefPtr mStorageArea; + nsCOMPtr mPrincipal; public: virtual StorageEvent* AsStorageEvent(); @@ -79,6 +82,18 @@ public: { return mStorageArea; } + + // Non WebIDL methods + void SetPrincipal(nsIPrincipal* aPrincipal) + { + MOZ_ASSERT(!mPrincipal); + mPrincipal = aPrincipal; + } + + nsIPrincipal* GetPrincipal() const + { + return mPrincipal; + } }; } // namespace dom diff --git a/dom/filesystem/CreateFileTask.cpp b/dom/filesystem/CreateFileTask.cpp index 06d271c7bc8b..2d4a40603a07 100644 --- a/dom/filesystem/CreateFileTask.cpp +++ b/dom/filesystem/CreateFileTask.cpp @@ -140,10 +140,8 @@ CreateFileTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aVal const FileSystemFileResponse& r = aValue.get_FileSystemFileResponse(); - aRv = NS_NewLocalFile(r.realPath(), true, getter_AddRefs(mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return; - } + mBlobImpl = static_cast(r.blobChild())->GetBlobImpl(); + MOZ_ASSERT(mBlobImpl); } void @@ -162,9 +160,10 @@ CreateFileTaskChild::HandlerCallback() return; } - RefPtr file = File::CreateFromFile(mFileSystem->GetParentObject(), - mTargetPath); + RefPtr file = File::Create(mFileSystem->GetParentObject(), mBlobImpl); mPromise->MaybeResolve(file); + + mBlobImpl = nullptr; mPromise = nullptr; } @@ -233,13 +232,10 @@ CreateFileTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const { AssertIsOnBackgroundThread(); - nsAutoString path; - aRv = mTargetPath->GetPath(path); - if (NS_WARN_IF(aRv.Failed())) { - return FileSystemDirectoryResponse(); - } - - return FileSystemFileResponse(path, EmptyString()); + RefPtr blobImpl = new BlobImplFile(mTargetPath); + BlobParent* blobParent = + BlobParent::GetOrCreate(mRequestParent->Manager(), blobImpl); + return FileSystemFileResponse(blobParent, nullptr); } nsresult diff --git a/dom/filesystem/CreateFileTask.h b/dom/filesystem/CreateFileTask.h index c3b2f17a240f..8ff473ad0f0a 100644 --- a/dom/filesystem/CreateFileTask.h +++ b/dom/filesystem/CreateFileTask.h @@ -59,6 +59,8 @@ private: RefPtr mPromise; nsCOMPtr mTargetPath; + // This is the content of what we want to store. Then, when the File is + // created, this will be used to store the new object. RefPtr mBlobImpl; // This is going to be the content of the file, received by createFile() diff --git a/dom/filesystem/Directory.h b/dom/filesystem/Directory.h index 5814b95994c5..0ffc05b43e5b 100644 --- a/dom/filesystem/Directory.h +++ b/dom/filesystem/Directory.h @@ -39,16 +39,6 @@ class Directory final , public nsWrapperCache { public: - struct FileOrDirectoryPath - { - nsString mPath; - - enum { - eFilePath, - eDirectoryPath - } mType; - }; - NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Directory) diff --git a/dom/filesystem/FileSystemTaskBase.cpp b/dom/filesystem/FileSystemTaskBase.cpp index c6643e4a4883..8f37ff596da9 100644 --- a/dom/filesystem/FileSystemTaskBase.cpp +++ b/dom/filesystem/FileSystemTaskBase.cpp @@ -205,8 +205,8 @@ FileSystemTaskChildBase::SetError(const nsresult& aErrorValue) */ FileSystemTaskParentBase::FileSystemTaskParentBase(FileSystemBase* aFileSystem, - const FileSystemParams& aParam, - FileSystemRequestParent* aParent) + const FileSystemParams& aParam, + FileSystemRequestParent* aParent) : mErrorValue(NS_OK) , mFileSystem(aFileSystem) , mRequestParent(aParent) diff --git a/dom/filesystem/GetDirectoryListingTask.cpp b/dom/filesystem/GetDirectoryListingTask.cpp index debfdf2c0bcd..7b3f220aa071 100644 --- a/dom/filesystem/GetDirectoryListingTask.cpp +++ b/dom/filesystem/GetDirectoryListingTask.cpp @@ -88,14 +88,22 @@ GetDirectoryListingTaskChild::GetRequestParams(const nsString& aSerializedDOMPat { mFileSystem->AssertIsOnOwningThread(); + // this is the real path. nsAutoString path; aRv = mTargetPath->GetPath(path); if (NS_WARN_IF(aRv.Failed())) { return FileSystemGetDirectoryListingParams(); } + // this is the dom path. + nsAutoString directoryPath; + mDirectory->GetPath(directoryPath, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemGetDirectoryListingParams(); + } + return FileSystemGetDirectoryListingParams(aSerializedDOMPath, path, - mFilters); + directoryPath, mFilters); } void @@ -110,22 +118,41 @@ GetDirectoryListingTaskChild::SetSuccessRequestResult(const FileSystemResponseVa for (uint32_t i = 0; i < r.data().Length(); ++i) { const FileSystemDirectoryListingResponseData& data = r.data()[i]; - Directory::FileOrDirectoryPath element; - - if (data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseFile) { - element.mType = Directory::FileOrDirectoryPath::eFilePath; - element.mPath = data.get_FileSystemDirectoryListingResponseFile().fileRealPath(); - } else { - MOZ_ASSERT(data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseDirectory); - - element.mType = Directory::FileOrDirectoryPath::eDirectoryPath; - element.mPath = data.get_FileSystemDirectoryListingResponseDirectory().directoryRealPath(); - } - - if (!mTargetData.AppendElement(element, fallible)) { + OwningFileOrDirectory* ofd = mTargetData.AppendElement(fallible); + if (!ofd) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } + + if (data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseFile) { + const FileSystemDirectoryListingResponseFile& d = + data.get_FileSystemDirectoryListingResponseFile(); + + RefPtr blobImpl = + static_cast(d.blobChild())->GetBlobImpl(); + MOZ_ASSERT(blobImpl); + + RefPtr file = File::Create(mFileSystem->GetParentObject(), blobImpl); + MOZ_ASSERT(file); + + ofd->SetAsFile() = file; + } else { + MOZ_ASSERT(data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseDirectory); + const FileSystemDirectoryListingResponseDirectory& d = + data.get_FileSystemDirectoryListingResponseDirectory(); + + nsCOMPtr path; + aRv = NS_NewLocalFile(d.directoryRealPath(), true, getter_AddRefs(path)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + RefPtr directory = + Directory::Create(mFileSystem->GetParentObject(), path, mFileSystem); + MOZ_ASSERT(directory); + + ofd->SetAsDirectory() = directory; + } } } @@ -145,81 +172,7 @@ GetDirectoryListingTaskChild::HandlerCallback() return; } - size_t count = mTargetData.Length(); - - nsAutoString directoryPath; - ErrorResult error; - mDirectory->GetPath(directoryPath, error); - if (NS_WARN_IF(error.Failed())) { - mPromise->MaybeReject(error.StealNSResult()); - mPromise = nullptr; - return; - } - - Sequence listing; - - if (!listing.SetLength(count, mozilla::fallible_t())) { - mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); - mPromise = nullptr; - return; - } - - for (unsigned i = 0; i < count; i++) { - nsCOMPtr path; - nsresult rv = NS_NewLocalFile(mTargetData[i].mPath, true, - getter_AddRefs(path)); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(rv); - mPromise = nullptr; - return; - } - -#ifdef DEBUG - nsCOMPtr rootPath; - rv = NS_NewLocalFile(mFileSystem->LocalOrDeviceStorageRootPath(), false, - getter_AddRefs(rootPath)); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(rv); - mPromise = nullptr; - return; - } - - MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, path)); -#endif - - if (mTargetData[i].mType == Directory::FileOrDirectoryPath::eDirectoryPath) { - RefPtr directory = - Directory::Create(mFileSystem->GetParentObject(), path, mFileSystem); - MOZ_ASSERT(directory); - - // Propogate mFilter onto sub-Directory object: - directory->SetContentFilters(mFilters); - listing[i].SetAsDirectory() = directory; - } else { - MOZ_ASSERT(mTargetData[i].mType == Directory::FileOrDirectoryPath::eFilePath); - - RefPtr file = - File::CreateFromFile(mFileSystem->GetParentObject(), path); - MOZ_ASSERT(file); - - nsAutoString filePath; - filePath.Assign(directoryPath); - - // This is specific for unix root filesystem. - if (!directoryPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) { - filePath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); - } - - nsAutoString name; - file->GetName(name); - filePath.Append(name); - file->SetPath(filePath); - - listing[i].SetAsFile() = file; - } - } - - mPromise->MaybeResolve(listing); + mPromise->MaybeResolve(mTargetData); mPromise = nullptr; } @@ -259,6 +212,7 @@ GetDirectoryListingTaskParent::GetDirectoryListingTaskParent(FileSystemBase* aFi const FileSystemGetDirectoryListingParams& aParam, FileSystemRequestParent* aParent) : FileSystemTaskParentBase(aFileSystem, aParam, aParent) + , mDOMPath(aParam.domPath()) , mFilters(aParam.filters()) { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); @@ -276,12 +230,35 @@ GetDirectoryListingTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const nsTArray inputs; for (unsigned i = 0; i < mTargetData.Length(); i++) { - if (mTargetData[i].mType == Directory::FileOrDirectoryPath::eFilePath) { + if (mTargetData[i].mType == FileOrDirectoryPath::eFilePath) { + nsCOMPtr path; + nsresult rv = NS_NewLocalFile(mTargetData[i].mPath, true, + getter_AddRefs(path)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return FileSystemErrorResponse(rv); + } + FileSystemDirectoryListingResponseFile fileData; - fileData.fileRealPath() = mTargetData[i].mPath; + RefPtr blobImpl = new BlobImplFile(path); + + nsAutoString filePath; + filePath.Assign(mDOMPath); + + // This is specific for unix root filesystem. + if (!mDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) { + filePath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); + } + + nsAutoString name; + blobImpl->GetName(name); + filePath.Append(name); + blobImpl->SetPath(filePath); + + fileData.blobParent() = + BlobParent::GetOrCreate(mRequestParent->Manager(), blobImpl); inputs.AppendElement(fileData); } else { - MOZ_ASSERT(mTargetData[i].mType == Directory::FileOrDirectoryPath::eDirectoryPath); + MOZ_ASSERT(mTargetData[i].mType == FileOrDirectoryPath::eDirectoryPath); FileSystemDirectoryListingResponseDirectory directoryData; directoryData.directoryRealPath() = mTargetData[i].mPath; inputs.AppendElement(directoryData); @@ -395,10 +372,10 @@ GetDirectoryListingTaskParent::IOWork() continue; } - Directory::FileOrDirectoryPath element; + FileOrDirectoryPath element; element.mPath = path; - element.mType = isDir ? Directory::FileOrDirectoryPath::eDirectoryPath - : Directory::FileOrDirectoryPath::eFilePath; + element.mType = isDir ? FileOrDirectoryPath::eDirectoryPath + : FileOrDirectoryPath::eFilePath; if (!mTargetData.AppendElement(element, fallible)) { return NS_ERROR_OUT_OF_MEMORY; diff --git a/dom/filesystem/GetDirectoryListingTask.h b/dom/filesystem/GetDirectoryListingTask.h index ead88a355c0e..9559b1f4810d 100644 --- a/dom/filesystem/GetDirectoryListingTask.h +++ b/dom/filesystem/GetDirectoryListingTask.h @@ -58,9 +58,7 @@ private: nsCOMPtr mTargetPath; nsString mFilters; - // We cannot store File or Directory objects bacause this object is created - // on a different thread and File and Directory are not thread-safe. - FallibleTArray mTargetData; + FallibleTArray mTargetData; }; class GetDirectoryListingTaskParent final : public FileSystemTaskParentBase @@ -87,11 +85,20 @@ private: IOWork() override; nsCOMPtr mTargetPath; + nsString mDOMPath; nsString mFilters; - // We cannot store File or Directory objects bacause this object is created - // on a different thread and File and Directory are not thread-safe. - FallibleTArray mTargetData; + struct FileOrDirectoryPath + { + nsString mPath; + + enum { + eFilePath, + eDirectoryPath + } mType; + }; + + FallibleTArray mTargetData; }; } // namespace dom diff --git a/dom/filesystem/GetFileOrDirectoryTask.cpp b/dom/filesystem/GetFileOrDirectoryTask.cpp index c330d6b6cf0f..8def35f73738 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.cpp +++ b/dom/filesystem/GetFileOrDirectoryTask.cpp @@ -58,7 +58,6 @@ GetFileOrDirectoryTaskChild::GetFileOrDirectoryTaskChild(FileSystemBase* aFileSy bool aDirectoryOnly) : FileSystemTaskChildBase(aFileSystem) , mTargetPath(aTargetPath) - , mIsDirectory(aDirectoryOnly) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); @@ -100,23 +99,26 @@ GetFileOrDirectoryTaskChild::SetSuccessRequestResult(const FileSystemResponseVal case FileSystemResponseValue::TFileSystemFileResponse: { FileSystemFileResponse r = aValue; - aRv = NS_NewLocalFile(r.realPath(), true, getter_AddRefs(mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return; - } + RefPtr blobImpl = + static_cast(r.blobChild())->GetBlobImpl(); + MOZ_ASSERT(blobImpl); - mIsDirectory = false; + mResultFile = File::Create(mFileSystem->GetParentObject(), blobImpl); + MOZ_ASSERT(mResultFile); break; } case FileSystemResponseValue::TFileSystemDirectoryResponse: { FileSystemDirectoryResponse r = aValue; - aRv = NS_NewLocalFile(r.realPath(), true, getter_AddRefs(mTargetPath)); + nsCOMPtr file; + aRv = NS_NewLocalFile(r.realPath(), true, getter_AddRefs(file)); if (NS_WARN_IF(aRv.Failed())) { return; } - mIsDirectory = true; + mResultDirectory = Directory::Create(mFileSystem->GetParentObject(), + file, mFileSystem); + MOZ_ASSERT(mResultDirectory); break; } default: { @@ -141,20 +143,16 @@ GetFileOrDirectoryTaskChild::HandlerCallback() return; } - if (mIsDirectory) { - RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), - mTargetPath, - mFileSystem); - MOZ_ASSERT(dir); - - mPromise->MaybeResolve(dir); + if (mResultDirectory) { + mPromise->MaybeResolve(mResultDirectory); + mResultDirectory = nullptr; mPromise = nullptr; return; } - RefPtr file = File::CreateFromFile(mFileSystem->GetParentObject(), - mTargetPath); - mPromise->MaybeResolve(file); + MOZ_ASSERT(mResultFile); + mPromise->MaybeResolve(mResultFile); + mResultFile = nullptr; mPromise = nullptr; } @@ -216,7 +214,10 @@ GetFileOrDirectoryTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const return FileSystemDirectoryResponse(path); } - return FileSystemFileResponse(path, EmptyString()); + RefPtr blobImpl = new BlobImplFile(mTargetPath); + BlobParent* blobParent = + BlobParent::GetOrCreate(mRequestParent->Manager(), blobImpl); + return FileSystemFileResponse(blobParent, nullptr); } nsresult diff --git a/dom/filesystem/GetFileOrDirectoryTask.h b/dom/filesystem/GetFileOrDirectoryTask.h index 686b9ebca050..21c6eb0037a5 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.h +++ b/dom/filesystem/GetFileOrDirectoryTask.h @@ -54,8 +54,8 @@ private: RefPtr mPromise; nsCOMPtr mTargetPath; - // Whether we get a directory. - bool mIsDirectory; + RefPtr mResultFile; + RefPtr mResultDirectory; }; class GetFileOrDirectoryTaskParent final : public FileSystemTaskParentBase diff --git a/dom/filesystem/GetFilesHelper.cpp b/dom/filesystem/GetFilesHelper.cpp index e6a180228ce8..22ed18af9f11 100644 --- a/dom/filesystem/GetFilesHelper.cpp +++ b/dom/filesystem/GetFilesHelper.cpp @@ -303,22 +303,10 @@ GetFilesHelper::RunMainThread() } // Create the sequence of Files. - for (uint32_t i = 0; i < mTargetPathArray.Length(); ++i) { - nsCOMPtr file; - mErrorResult = - NS_NewLocalFile(mTargetPathArray[i].mRealPath, true, - getter_AddRefs(file)); - if (NS_WARN_IF(NS_FAILED(mErrorResult))) { - mFiles.Clear(); - return; - } - - RefPtr domFile = - File::CreateFromFile(mGlobal, file); + for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) { + RefPtr domFile = File::Create(mGlobal, mTargetBlobImplArray[i]); MOZ_ASSERT(domFile); - domFile->SetPath(mTargetPathArray[i].mDomPath); - if (!mFiles.AppendElement(domFile, fallible)) { mErrorResult = NS_ERROR_OUT_OF_MEMORY; mFiles.Clear(); @@ -395,16 +383,13 @@ GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile) domPath.Append(leafName); if (isFile) { - FileData* data = mTargetPathArray.AppendElement(fallible); - if (!data) { + RefPtr blobImpl = new BlobImplFile(currFile); + blobImpl->SetPath(domPath); + + if (!mTargetBlobImplArray.AppendElement(blobImpl, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } - if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data->mRealPath)))) { - continue; - } - - data->mDomPath = domPath; continue; } diff --git a/dom/filesystem/GetFilesHelper.h b/dom/filesystem/GetFilesHelper.h index 9a08120b08e7..a1bb1b27a6da 100644 --- a/dom/filesystem/GetFilesHelper.h +++ b/dom/filesystem/GetFilesHelper.h @@ -63,13 +63,8 @@ protected: bool mRecursiveFlag; - // We populate this array in the I/O thread with the paths of the Files that - // we want to send as result to the promise objects. - struct FileData { - nsString mDomPath; - nsString mRealPath; - }; - FallibleTArray mTargetPathArray; + // We populate this array in the I/O thread with the BlobImpl. + FallibleTArray> mTargetBlobImplArray; nsTHashtable mExploredDirectories; }; diff --git a/dom/filesystem/GetFilesTask.cpp b/dom/filesystem/GetFilesTask.cpp index e44505f31d2e..fe7ca87a7e04 100644 --- a/dom/filesystem/GetFilesTask.cpp +++ b/dom/filesystem/GetFilesTask.cpp @@ -122,8 +122,11 @@ GetFilesTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue for (uint32_t i = 0; i < r.data().Length(); ++i) { const FileSystemFileResponse& data = r.data()[i]; - mTargetData[i].mRealPath = data.realPath(); - mTargetData[i].mDOMPath = data.domPath(); + RefPtr blobImpl = + static_cast(data.blobChild())->GetBlobImpl(); + MOZ_ASSERT(blobImpl); + + mTargetData[i] = File::Create(mFileSystem->GetParentObject(), blobImpl); } } @@ -142,48 +145,7 @@ GetFilesTaskChild::HandlerCallback() return; } - size_t count = mTargetData.Length(); - - Sequence> listing; - - if (!listing.SetLength(count, mozilla::fallible_t())) { - mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); - mPromise = nullptr; - return; - } - - for (unsigned i = 0; i < count; i++) { - nsCOMPtr path; - nsresult rv = NS_NewLocalFile(mTargetData[i].mRealPath, true, - getter_AddRefs(path)); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - mPromise = nullptr; - return; - } - -#ifdef DEBUG - nsCOMPtr rootPath; - rv = NS_NewLocalFile(mFileSystem->LocalOrDeviceStorageRootPath(), false, - getter_AddRefs(rootPath)); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - mPromise = nullptr; - return; - } - - MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, path)); -#endif - - RefPtr file = - File::CreateFromFile(mFileSystem->GetParentObject(), path); - MOZ_ASSERT(file); - file->SetPath(mTargetData[i].mDOMPath); - - listing[i] = file; - } - - mPromise->MaybeResolve(listing); + mPromise->MaybeResolve(mTargetData); mPromise = nullptr; } @@ -239,17 +201,17 @@ GetFilesTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const InfallibleTArray blobs; FallibleTArray inputs; - if (!inputs.SetLength(mTargetPathArray.Length(), mozilla::fallible_t())) { + if (!inputs.SetLength(mTargetBlobImplArray.Length(), mozilla::fallible_t())) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); FileSystemFilesResponse response; return response; } - for (unsigned i = 0; i < mTargetPathArray.Length(); i++) { - FileSystemFileResponse fileData; - fileData.realPath() = mTargetPathArray[i].mRealPath; - fileData.domPath() = mTargetPathArray[i].mDomPath; - inputs[i] = fileData; + for (unsigned i = 0; i < mTargetBlobImplArray.Length(); i++) { + BlobParent* blobParent = + BlobParent::GetOrCreate(mRequestParent->Manager(), + mTargetBlobImplArray[i]); + inputs[i] = FileSystemFileResponse(blobParent, nullptr); } FileSystemFilesResponse response; diff --git a/dom/filesystem/GetFilesTask.h b/dom/filesystem/GetFilesTask.h index 42c0ce03d929..545ff2ccd532 100644 --- a/dom/filesystem/GetFilesTask.h +++ b/dom/filesystem/GetFilesTask.h @@ -60,12 +60,7 @@ private: bool mRecursiveFlag; // We store the fullpath and the dom path of Files. - struct FileData { - nsString mRealPath; - nsString mDOMPath; - }; - - FallibleTArray mTargetData; + FallibleTArray> mTargetData; }; class GetFilesTaskParent final : public FileSystemTaskParentBase diff --git a/dom/filesystem/PFileSystemParams.ipdlh b/dom/filesystem/PFileSystemParams.ipdlh index 6a01d2635e31..763945975e8d 100644 --- a/dom/filesystem/PFileSystemParams.ipdlh +++ b/dom/filesystem/PFileSystemParams.ipdlh @@ -31,6 +31,7 @@ struct FileSystemGetDirectoryListingParams { nsString filesystem; nsString realPath; + nsString domPath; // 'filters' could be an array rather than a semicolon separated string // (we'd then use InfallibleTArray internally), but that is diff --git a/dom/filesystem/PFileSystemRequest.ipdl b/dom/filesystem/PFileSystemRequest.ipdl index 4966dae575ed..476052af4fc3 100644 --- a/dom/filesystem/PFileSystemRequest.ipdl +++ b/dom/filesystem/PFileSystemRequest.ipdl @@ -12,8 +12,7 @@ namespace dom { struct FileSystemFileResponse { - nsString realPath; - nsString domPath; + PBlob blob; }; struct FileSystemDirectoryResponse @@ -23,8 +22,7 @@ struct FileSystemDirectoryResponse struct FileSystemDirectoryListingResponseFile { - // This is the full real path for the file that we are sending via IPC. - nsString fileRealPath; + PBlob blob; }; struct FileSystemDirectoryListingResponseDirectory diff --git a/dom/interfaces/storage/nsIDOMStorageManager.idl b/dom/interfaces/storage/nsIDOMStorageManager.idl index 1008477c099e..45f79f301be0 100644 --- a/dom/interfaces/storage/nsIDOMStorageManager.idl +++ b/dom/interfaces/storage/nsIDOMStorageManager.idl @@ -20,8 +20,15 @@ interface nsIDOMStorageManager : nsISupports /** * This starts async preloading of a storage cache for scope * defined by the principal. + * + * Because of how multi-e10s support was implemented in bug 1285898, the + * StorageCache instance can no longer use a timer to keep itself alive. So a + * Storage instance is returned if precaching believes the principal may have + * localStorage data. (Previously the StorageCache would be brought into + * existence and kept alive by the timer so that it could be returned if a + * call to createStorage was made due to a request by the page.) */ - void precacheStorage(in nsIPrincipal aPrincipal); + nsIDOMStorage precacheStorage(in nsIPrincipal aPrincipal); /** * Returns instance of DOM storage object for given principal. diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 993f63b69287..34e8e83e6372 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -35,6 +35,7 @@ #include "mozilla/dom/PCrashReporterChild.h" #include "mozilla/dom/ProcessGlobal.h" #include "mozilla/dom/PushNotifier.h" +#include "mozilla/dom/Storage.h" #include "mozilla/dom/StorageIPC.h" #include "mozilla/dom/TabGroup.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" @@ -1398,6 +1399,15 @@ ContentChild::RecvBidiKeyboardNotify(const bool& aIsLangRTL, return IPC_OK(); } +static StaticRefPtr gFirstIdleTask; + +static void +FirstIdle(void) +{ + MOZ_ASSERT(gFirstIdleTask); + gFirstIdleTask = nullptr; + ContentChild::GetSingleton()->SendFirstIdle(); +} mozilla::jsipc::PJavaScriptChild * ContentChild::AllocPJavaScriptChild() @@ -1457,6 +1467,15 @@ ContentChild::RecvPBrowserConstructor(PBrowserChild* aActor, { MOZ_ASSERT(!IsShuttingDown()); + static bool hasRunOnce = false; + if (!hasRunOnce) { + hasRunOnce = true; + MOZ_ASSERT(!gFirstIdleTask); + RefPtr firstIdleTask = NewCancelableRunnableFunction(FirstIdle); + gFirstIdleTask = firstIdleTask; + NS_IdleDispatchToCurrentThread(firstIdleTask.forget()); + } + return nsIContentChild::RecvPBrowserConstructor(aActor, aTabId, aContext, aChromeFlags, aCpID, aIsForBrowser); } @@ -1995,6 +2014,9 @@ ContentChild::ActorDestroy(ActorDestroyReason why) // keep persistent state. ProcessChild::QuickExit(); #else + if (gFirstIdleTask) { + gFirstIdleTask->Cancel(); + } nsHostObjectProtocolHandler::RemoveDataEntries(); @@ -3029,6 +3051,20 @@ ContentChild::RecvBlobURLUnregistration(const nsCString& aURI) return IPC_OK(); } +mozilla::ipc::IPCResult +ContentChild::RecvDispatchLocalStorageChange(const nsString& aDocumentURI, + const nsString& aKey, + const nsString& aOldValue, + const nsString& aNewValue, + const IPC::Principal& aPrincipal, + const bool& aIsPrivate) +{ + Storage::DispatchStorageEvent(Storage::LocalStorage, + aDocumentURI, aKey, aOldValue, aNewValue, + aPrincipal, aIsPrivate, nullptr, true); + return IPC_OK(); +} + #if defined(XP_WIN) && defined(ACCESSIBILITY) bool ContentChild::SendGetA11yContentId() diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index 2b12f638d948..5ed147dee780 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -408,6 +408,14 @@ public: virtual mozilla::ipc::IPCResult RecvInitBlobURLs(nsTArray&& aRegistations) override; + virtual mozilla::ipc::IPCResult + RecvDispatchLocalStorageChange(const nsString& aDocumentURI, + const nsString& aKey, + const nsString& aOldValue, + const nsString& aNewValue, + const IPC::Principal& aPrincipal, + const bool& aIsPrivate) override; + virtual mozilla::ipc::IPCResult RecvLastPrivateDocShellDestroyed() override; virtual mozilla::ipc::IPCResult RecvFilePathUpdate(const nsString& aStorageType, diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 41ff9dfd4dd5..b26110f6685b 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -51,6 +51,7 @@ #include "mozilla/dom/PContentPermissionRequestParent.h" #include "mozilla/dom/PCycleCollectWithLogsParent.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/Storage.h" #include "mozilla/dom/StorageIPC.h" #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h" #include "mozilla/dom/power/PowerManagerService.h" @@ -156,6 +157,7 @@ #include "nsThreadUtils.h" #include "nsToolkitCompsCID.h" #include "nsWidgetsCID.h" +#include "PreallocatedProcessManager.h" #include "ProcessPriorityManager.h" #include "SandboxHal.h" #include "ScreenManagerParent.h" @@ -476,6 +478,23 @@ static const char* sObserverTopics[] = { "cacheservice:empty-cache", }; +// PreallocateProcess is called by the PreallocatedProcessManager. +// ContentParent then takes this process back within GetNewOrUsedBrowserProcess. +/*static*/ already_AddRefed +ContentParent::PreallocateProcess() +{ + RefPtr process = + new ContentParent(/* aOpener = */ nullptr, + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)); + + if (!process->LaunchSubprocess(PROCESS_PRIORITY_PREALLOC)) { + return nullptr; + } + + process->Init(); + return process.forget(); +} + /*static*/ void ContentParent::StartUp() { @@ -509,6 +528,9 @@ ContentParent::StartUp() BackgroundChild::Startup(); + // Try to preallocate a process that we can use later. + PreallocatedProcessManager::AllocateAfterDelay(); + sDisableUnsafeCPOWWarnings = PR_GetEnv("DISABLE_UNSAFE_CPOW_WARNINGS"); #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX) @@ -575,6 +597,74 @@ ContentParent::JoinAllSubprocesses() sCanLaunchSubprocesses = false; } +/*static*/ uint32_t +ContentParent::GetPoolSize(const nsAString& aContentProcessType) +{ + if (!sBrowserContentParents) { + return 0; + } + + nsTArray* parents = + sBrowserContentParents->Get(aContentProcessType); + + return parents ? parents->Length() : 0; +} + + +/*static*/ nsTArray& +ContentParent::GetOrCreatePool(const nsAString& aContentProcessType) +{ + if (!sBrowserContentParents) { + sBrowserContentParents = + new nsClassHashtable>; + } + + return *sBrowserContentParents->LookupOrAdd(aContentProcessType); +} + +/*static*/ uint32_t +ContentParent::GetMaxProcessCount(const nsAString& aContentProcessType) +{ + int32_t maxContentParents; + nsAutoCString processCountPref("dom.ipc.processCount."); + processCountPref.Append(NS_ConvertUTF16toUTF8(aContentProcessType)); + if (NS_FAILED(Preferences::GetInt(processCountPref.get(), &maxContentParents))) { + maxContentParents = Preferences::GetInt("dom.ipc.processCount", 1); + } + + if (maxContentParents < 1) { + maxContentParents = 1; + } + + return static_cast(maxContentParents); +} + +/*static*/ bool +ContentParent::IsMaxProcessCountReached(const nsAString& aContentProcessType) +{ + return GetPoolSize(aContentProcessType) >= GetMaxProcessCount(aContentProcessType); +} + +/*static*/ already_AddRefed +ContentParent::RandomSelect(const nsTArray& aContentParents, + ContentParent* aOpener, int32_t aMaxContentParents) +{ + uint32_t maxSelectable = std::min(static_cast(aContentParents.Length()), + static_cast(aMaxContentParents)); + uint32_t startIdx = rand() % maxSelectable; + uint32_t currIdx = startIdx; + do { + RefPtr p = aContentParents[currIdx]; + NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sBrowserContentParents?"); + if (p->mOpener == aOpener) { + return p.forget(); + } + currIdx = (currIdx + 1) % maxSelectable; + } while (currIdx != startIdx); + + return nullptr; +} + /*static*/ already_AddRefed ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType, ProcessPriority aPriority, @@ -585,58 +675,42 @@ ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType, if (aNew) { *aNew = false; } - - if (!sBrowserContentParents) { - sBrowserContentParents = - new nsClassHashtable>; - } - // Decide which pool of content parents we are going to be pulling from based // on the aRemoteType and aLargeAllocationProcess flag. nsAutoString contentProcessType(aLargeAllocationProcess ? NS_LITERAL_STRING(LARGE_ALLOCATION_REMOTE_TYPE) : aRemoteType); - nsTArray* contentParents = - sBrowserContentParents->LookupOrAdd(contentProcessType); - int32_t maxContentParents; - nsAutoCString processCountPref("dom.ipc.processCount."); - processCountPref.Append(NS_ConvertUTF16toUTF8(contentProcessType)); - if (NS_FAILED(Preferences::GetInt(processCountPref.get(), &maxContentParents))) { - maxContentParents = Preferences::GetInt("dom.ipc.processCount", 1); + nsTArray& contentParents = GetOrCreatePool(contentProcessType); + + uint32_t maxContentParents = GetMaxProcessCount(contentProcessType); + + RefPtr p; + if (contentParents.Length() >= uint32_t(maxContentParents) && + (p = RandomSelect(contentParents, aOpener, maxContentParents))) { + return p.forget(); } - if (maxContentParents < 1) { - maxContentParents = 1; + // Try to take the preallocated process only for the default process type. + if (contentProcessType.Equals(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)) && + (p = PreallocatedProcessManager::Take())) { + // For pre-allocated process we have not set the opener yet. + p->mOpener = aOpener; + } else { + p = new ContentParent(aOpener, contentProcessType); + + if (aNew) { + *aNew = true; + } + + if (!p->LaunchSubprocess(aPriority)) { + return nullptr; + } + + p->Init(); } - if (contentParents->Length() >= uint32_t(maxContentParents)) { - uint32_t maxSelectable = std::min(static_cast(contentParents->Length()), - static_cast(maxContentParents)); - uint32_t startIdx = rand() % maxSelectable; - uint32_t currIdx = startIdx; - do { - RefPtr p = (*contentParents)[currIdx]; - NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sBrowserContentParents?"); - if (p->mOpener == aOpener) { - return p.forget(); - } - currIdx = (currIdx + 1) % maxSelectable; - } while (currIdx != startIdx); - } - - RefPtr p = new ContentParent(aOpener, contentProcessType); - if (aNew) { - *aNew = true; - } - - if (!p->LaunchSubprocess(aPriority)) { - return nullptr; - } - - p->Init(); - - contentParents->AppendElement(p); + contentParents.AppendElement(p); return p.forget(); } @@ -2250,6 +2324,17 @@ ContentParent::RecvGetShowPasswordSetting(bool* showPassword) return IPC_OK(); } +mozilla::ipc::IPCResult +ContentParent::RecvFirstIdle() +{ + // When the ContentChild goes idle, it sends us a FirstIdle message + // which we use as a good time to prelaunch another process. If we + // prelaunch any sooner than this, then we'll be competing with the + // child process and slowing it down. + PreallocatedProcessManager::AllocateAfterDelay(); + return IPC_OK(); +} + mozilla::ipc::IPCResult ContentParent::RecvAudioChannelChangeDefVolChannel(const int32_t& aChannel, const bool& aHidden) @@ -2552,6 +2637,13 @@ ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline, SerializeURI(nullptr, *aUserContentCSSURL); } + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + nsAutoString cpId; + cpId.AppendInt(static_cast(this->ChildID())); + obs->NotifyObservers(static_cast(this), "ipc:content-initializing", cpId.get()); + } + return IPC_OK(); } @@ -4647,6 +4739,25 @@ ContentParent::RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aUR return IPC_OK(); } +mozilla::ipc::IPCResult +ContentParent::RecvBroadcastLocalStorageChange(const nsString& aDocumentURI, + const nsString& aKey, + const nsString& aOldValue, + const nsString& aNewValue, + const Principal& aPrincipal, + const bool& aIsPrivate) +{ + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + if (cp != this) { + Unused << cp->SendDispatchLocalStorageChange( + nsString(aDocumentURI), nsString(aKey), nsString(aOldValue), + nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate); + } + } + + return IPC_OK(); +} + mozilla::ipc::IPCResult ContentParent::RecvGetA11yContentId(uint32_t* aContentId) { diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 03ff24a52c38..7dfb1b950f9a 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -63,6 +63,8 @@ class SandboxBroker; class SandboxBrokerPolicyFactory; #endif +class PreallocatedProcessManagerImpl; + namespace embedding { class PrintingParent; } @@ -116,10 +118,17 @@ class ContentParent final : public PContentParent typedef mozilla::ipc::PrincipalInfo PrincipalInfo; typedef mozilla::dom::ClonedMessageData ClonedMessageData; + friend class mozilla::PreallocatedProcessManagerImpl; + public: virtual bool IsContentParent() const override { return true; } + /** + * Create a subprocess suitable for use later as a content process. + */ + static already_AddRefed PreallocateProcess(); + /** * Start up the content-process machinery. This might include * scheduling pre-launch tasks. @@ -137,6 +146,22 @@ public: */ static void JoinAllSubprocesses(); + static uint32_t GetPoolSize(const nsAString& aContentProcessType); + + static uint32_t GetMaxProcessCount(const nsAString& aContentProcessType); + + static bool IsMaxProcessCountReached(const nsAString& aContentProcessType); + + /** + * Picks a random content parent from |aContentParents| with a given |aOpener| + * respecting the index limit set by |aMaxContentParents|. + * Returns null if non available. + */ + static already_AddRefed + RandomSelect(const nsTArray& aContentParents, + ContentParent* aOpener, + int32_t maxContentParents); + /** * Get or create a content process for: * 1. browser iframe @@ -552,6 +577,14 @@ public: virtual mozilla::ipc::IPCResult RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override; + virtual mozilla::ipc::IPCResult + RecvBroadcastLocalStorageChange(const nsString& aDocumentURI, + const nsString& aKey, + const nsString& aOldValue, + const nsString& aNewValue, + const IPC::Principal& aPrincipal, + const bool& aIsPrivate) override; + virtual mozilla::ipc::IPCResult RecvGetA11yContentId(uint32_t* aContentId) override; @@ -713,6 +746,11 @@ private: TabParent* aTopLevel, const TabId& aTabId, uint64_t* aId); + /** + * Get or create the corresponding content parent array to |aContentProcessType|. + */ + static nsTArray& GetOrCreatePool(const nsAString& aContentProcessType); + virtual mozilla::ipc::IPCResult RecvInitBackground(Endpoint&& aEndpoint) override; virtual mozilla::ipc::IPCResult RecvGetProcessAttributes(ContentParentId* aCpId, @@ -965,6 +1003,8 @@ private: virtual mozilla::ipc::IPCResult RecvPrivateDocShellsExist(const bool& aExist) override; + virtual mozilla::ipc::IPCResult RecvFirstIdle() override; + virtual mozilla::ipc::IPCResult RecvAudioChannelChangeDefVolChannel(const int32_t& aChannel, const bool& aHidden) override; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 3f2f819eaaa1..78f4bb9982ef 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -643,6 +643,12 @@ child: async BlobURLUnregistration(nsCString aURI); + async DispatchLocalStorageChange(nsString documentURI, + nsString key, + nsString oldValue, + nsString newValue, + Principal principal, + bool isPrivate); async GMPsChanged(GMPCapabilityData[] capabilities); @@ -896,6 +902,9 @@ parent: // Notify the parent of the presence or absence of private docshells async PrivateDocShellsExist(bool aExist); + // Tell the parent that the child has gone idle for the first time. + async FirstIdle(); + async AudioChannelServiceStatus(bool aActiveTelephonyChannel, bool aContentOrNormalChannel, bool aAnyActiveChannel); @@ -1148,6 +1157,13 @@ parent: async UnstoreAndBroadcastBlobURLUnregistration(nsCString url); + async BroadcastLocalStorageChange(nsString documentURI, + nsString key, + nsString oldValue, + nsString newValue, + Principal principal, + bool isPrivate); + /** * Messages for communicating child Telemetry to the parent process */ diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp new file mode 100644 index 000000000000..756e069933a7 --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/PreallocatedProcessManager.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsIPropertyBag2.h" +#include "ProcessPriorityManager.h" +#include "nsServiceManagerUtils.h" + +// This number is fairly arbitrary ... the intention is to put off +// launching another app process until the last one has finished +// loading its content, to reduce CPU/memory/IO contention. +#define DEFAULT_ALLOCATE_DELAY 1000 + +using namespace mozilla; +using namespace mozilla::hal; +using namespace mozilla::dom; + +namespace mozilla { + +/** + * This singleton class implements the static methods on + * PreallocatedProcessManager. + */ +class PreallocatedProcessManagerImpl final + : public nsIObserver +{ +public: + static PreallocatedProcessManagerImpl* Singleton(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + // See comments on PreallocatedProcessManager for these methods. + void AllocateAfterDelay(); + void AllocateOnIdle(); + void AllocateNow(); + already_AddRefed Take(); + +private: + static mozilla::StaticRefPtr sSingleton; + + PreallocatedProcessManagerImpl(); + ~PreallocatedProcessManagerImpl() {} + DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl); + + void Init(); + + void RereadPrefs(); + void Enable(); + void Disable(); + void CloseProcess(); + + void ObserveProcessShutdown(nsISupports* aSubject); + + bool mEnabled; + bool mShutdown; + RefPtr mPreallocatedProcess; +}; + +/* static */ StaticRefPtr +PreallocatedProcessManagerImpl::sSingleton; + +/* static */ PreallocatedProcessManagerImpl* +PreallocatedProcessManagerImpl::Singleton() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!sSingleton) { + sSingleton = new PreallocatedProcessManagerImpl(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + + // First time when we init sSingleton, the pref database might not be in a + // reliable state (we are too early), so despite dom.ipc.processPrelaunch.enabled + // is set to true Preferences::GetBool will return false (it cannot find the pref). + // Since Init() above will be called only once, and the pref will not be changed, + // the manger will stay disabled. To prevent that let's re-read the pref each time + // someone accessing the manager singleton. This is a hack but this is not a hot code + // so it should be fine. + sSingleton->RereadPrefs(); + + return sSingleton; +} + +NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver) + +PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl() + : mEnabled(false) + , mShutdown(false) +{} + +void +PreallocatedProcessManagerImpl::Init() +{ + Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled"); + // We have to respect processCount at all time. This is especially important + // for testing. + Preferences::AddStrongObserver(this, "dom.ipc.processCount"); + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->AddObserver(this, "ipc:content-shutdown", + /* weakRef = */ false); + os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + /* weakRef = */ false); + } + RereadPrefs(); +} + +NS_IMETHODIMP +PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!strcmp("ipc:content-shutdown", aTopic)) { + ObserveProcessShutdown(aSubject); + } else if (!strcmp("nsPref:changed", aTopic)) { + // The only other observer we registered was for our prefs. + RereadPrefs(); + } else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { + Preferences::RemoveObserver(this, "dom.ipc.processPrelaunch.enabled"); + Preferences::RemoveObserver(this, "dom.ipc.processCount"); + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "ipc:content-shutdown"); + os->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + } else { + MOZ_ASSERT(false); + } + + return NS_OK; +} + +void +PreallocatedProcessManagerImpl::RereadPrefs() +{ + if (Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) { + Enable(); + } else { + Disable(); + } + + if (ContentParent::IsMaxProcessCountReached(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE))) { + CloseProcess(); + } +} + +already_AddRefed +PreallocatedProcessManagerImpl::Take() +{ + return mPreallocatedProcess.forget(); +} + +void +PreallocatedProcessManagerImpl::Enable() +{ + if (mEnabled) { + return; + } + + mEnabled = true; + AllocateAfterDelay(); +} + +void +PreallocatedProcessManagerImpl::AllocateAfterDelay() +{ + if (!mEnabled || mPreallocatedProcess) { + return; + } + + // Originally AllocateOnIdle() was post here, but since the gecko parent + // message loop in practice never goes idle, that didn't work out well. + // Let's just launch the process after the delay. + NS_DelayedDispatchToCurrentThread( + NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateNow), + Preferences::GetUint("dom.ipc.processPrelaunch.delayMs", + DEFAULT_ALLOCATE_DELAY)); +} + +void +PreallocatedProcessManagerImpl::AllocateOnIdle() +{ + if (!mEnabled || mPreallocatedProcess) { + return; + } + + NS_IdleDispatchToCurrentThread(NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateNow)); +} + +void +PreallocatedProcessManagerImpl::AllocateNow() +{ + if (!mEnabled || mPreallocatedProcess || + ContentParent::IsMaxProcessCountReached(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE))) { + return; + } + + mPreallocatedProcess = ContentParent::PreallocateProcess(); +} + +void +PreallocatedProcessManagerImpl::Disable() +{ + if (!mEnabled) { + return; + } + + mEnabled = false; + CloseProcess(); +} + +void +PreallocatedProcessManagerImpl::CloseProcess() +{ + if (mPreallocatedProcess) { + mPreallocatedProcess->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE); + mPreallocatedProcess = nullptr; + } +} + +void +PreallocatedProcessManagerImpl::ObserveProcessShutdown(nsISupports* aSubject) +{ + if (!mPreallocatedProcess) { + return; + } + + nsCOMPtr props = do_QueryInterface(aSubject); + NS_ENSURE_TRUE_VOID(props); + + uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; + props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID); + NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN); + + if (childID == mPreallocatedProcess->ChildID()) { + mPreallocatedProcess = nullptr; + } +} + +inline PreallocatedProcessManagerImpl* GetPPMImpl() +{ + return PreallocatedProcessManagerImpl::Singleton(); +} + +/* static */ void +PreallocatedProcessManager::AllocateAfterDelay() +{ + GetPPMImpl()->AllocateAfterDelay(); +} + +/* static */ void +PreallocatedProcessManager::AllocateOnIdle() +{ + GetPPMImpl()->AllocateOnIdle(); +} + +/* static */ void +PreallocatedProcessManager::AllocateNow() +{ + GetPPMImpl()->AllocateNow(); +} + +/* static */ already_AddRefed +PreallocatedProcessManager::Take() +{ + return GetPPMImpl()->Take(); +} + +} // namespace mozilla diff --git a/dom/ipc/PreallocatedProcessManager.h b/dom/ipc/PreallocatedProcessManager.h new file mode 100644 index 000000000000..4acfa093f645 --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PreallocatedProcessManager_h +#define mozilla_PreallocatedProcessManager_h + +#include "base/basictypes.h" +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { +namespace dom { +class ContentParent; +} // namespace dom + +/** + * This class manages a ContentParent that it starts up ahead of any particular + * need. You can then call Take() to get this process and use it. Since we + * already started it up, it should be ready for use faster than if you'd + * created the process when you needed it. + * + * This class watches the dom.ipc.processPrelaunch.enabled pref. If it changes + * from false to true, it preallocates a process. If it changes from true to + * false, it kills the preallocated process, if any. + * + * We don't expect this pref to flip between true and false in production, but + * flipping the pref is important for tests. + */ +class PreallocatedProcessManager final +{ + typedef mozilla::dom::ContentParent ContentParent; + +public: + /** + * Create a process after a delay. We wait for a period of time (specified + * by the dom.ipc.processPrelaunch.delayMs pref), then wait for this process + * to go idle, then allocate the new process. + * + * If the dom.ipc.processPrelaunch.enabled pref is false, or if we already + * have a preallocated process, this function does nothing. + */ + static void AllocateAfterDelay(); + + /** + * Create a process once this process goes idle. + * + * If the dom.ipc.processPrelaunch.enabled pref is false, or if we already + * have a preallocated process, this function does nothing. + */ + static void AllocateOnIdle(); + + /** + * Create a process right now. + * + * If the dom.ipc.processPrelaunch.enabled pref is false, or if we already + * have a preallocated process, this function does nothing. + */ + static void AllocateNow(); + + /** + * Take the preallocated process, if we have one. If we don't have one, this + * returns null. + * + * If you call Take() twice in a row, the second call is guaranteed to return + * null. + * + * After you Take() the preallocated process, you need to call one of the + * Allocate* functions (or change the dom.ipc.processPrelaunch pref from + * false to true) before we'll create a new process. + */ + static already_AddRefed Take(); + +private: + PreallocatedProcessManager(); + DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager); +}; + +} // namespace mozilla + +#endif // defined mozilla_PreallocatedProcessManager_h diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 5c2badde9349..5d52c971a058 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -3296,6 +3296,18 @@ TabParent::RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex) return IPC_OK(); } +void +TabParent::LiveResizeStarted() +{ + SuppressDisplayport(true); +} + +void +TabParent::LiveResizeStopped() +{ + SuppressDisplayport(false); +} + NS_IMETHODIMP FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo) { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index f33993f33b13..548ec4a32ca4 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -8,6 +8,7 @@ #define mozilla_tabs_TabParent_h #include "js/TypeDecls.h" +#include "LiveResizeListener.h" #include "mozilla/ContentCache.h" #include "mozilla/dom/AudioChannelBinding.h" #include "mozilla/dom/ipc/IdType.h" @@ -91,6 +92,7 @@ class TabParent final : public PBrowserParent , public TabContext , public nsAPostRefreshObserver , public nsIWebBrowserPersistable + , public LiveResizeListener { typedef mozilla::dom::ClonedMessageData ClonedMessageData; @@ -593,6 +595,10 @@ public: mozilla::ipc::IPCResult RecvEnsureLayersConnected() override; + // LiveResizeListener implementation + void LiveResizeStarted() override; + void LiveResizeStopped() override; + protected: bool ReceiveMessage(const nsString& aMessage, bool aSync, diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index 95b28c1838b5..452e1fe45166 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -39,6 +39,7 @@ EXPORTS.mozilla.dom += [ ] EXPORTS.mozilla += [ + 'PreallocatedProcessManager.h', 'ProcessHangMonitor.h', 'ProcessHangMonitorIPC.h', 'ProcessPriorityManager.h', @@ -58,6 +59,7 @@ UNIFIED_SOURCES += [ 'nsIContentChild.cpp', 'nsIContentParent.cpp', 'PermissionMessageUtils.cpp', + 'PreallocatedProcessManager.cpp', 'ProcessPriorityManager.cpp', 'ScreenManagerParent.cpp', 'StructuredCloneData.cpp', diff --git a/dom/ipc/tests/mochitest.ini b/dom/ipc/tests/mochitest.ini index acd6a595c114..fba9c182f51d 100644 --- a/dom/ipc/tests/mochitest.ini +++ b/dom/ipc/tests/mochitest.ini @@ -21,3 +21,5 @@ skip-if = !e10s support-files = blob_verify.sjs !/dom/canvas/test/captureStream_common.js +[test_Preallocated.html] +skip-if = !e10s diff --git a/dom/ipc/tests/test_Preallocated.html b/dom/ipc/tests/test_Preallocated.html new file mode 100644 index 000000000000..c191bcd60bcd --- /dev/null +++ b/dom/ipc/tests/test_Preallocated.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp index 7e7771e46c16..ac76ec9cbf64 100644 --- a/dom/messagechannel/MessagePort.cpp +++ b/dom/messagechannel/MessagePort.cpp @@ -63,7 +63,14 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(mPort); + NS_ASSERT_OWNINGTHREAD(Runnable); + + // The port can be cycle collected while this runnable is pending in + // the event queue. + if (!mPort) { + return NS_OK; + } + MOZ_ASSERT(mPort->mPostMessageRunnable == this); nsresult rv = DispatchMessage(); @@ -81,6 +88,8 @@ public: nsresult Cancel() override { + NS_ASSERT_OWNINGTHREAD(Runnable); + mPort = nullptr; mData = nullptr; return NS_OK; @@ -90,6 +99,8 @@ private: nsresult DispatchMessage() const { + NS_ASSERT_OWNINGTHREAD(Runnable); + nsCOMPtr globalObject = mPort->GetParentObject(); AutoJSAPI jsapi; diff --git a/dom/security/test/csp/file_child-src_worker-redirect.html b/dom/security/test/csp/file_child-src_worker-redirect.html index 188f173b8632..b0029935c20e 100644 --- a/dom/security/test/csp/file_child-src_worker-redirect.html +++ b/dom/security/test/csp/file_child-src_worker-redirect.html @@ -23,11 +23,8 @@ ); worker.onerror = function(error) { - var msg = error.message; - if (msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/)) { - // this means CSP blocked it - msg = "blocked"; - } + // this means CSP blocked it + var msg = !("message" in error) ? "blocked" : e.message; window.parent.postMessage({id:page_id, message:msg}, 'http://mochi.test:8888'); error.preventDefault(); }; diff --git a/dom/security/test/general/test_block_script_wrong_mime.html b/dom/security/test/general/test_block_script_wrong_mime.html index 5d7c11b0d3b8..04bdbb225dfb 100644 --- a/dom/security/test/general/test_block_script_wrong_mime.html +++ b/dom/security/test/general/test_block_script_wrong_mime.html @@ -53,9 +53,6 @@ function testWorker([mime, shouldLoad]) { }; worker.onerror = (error) => { ok(!shouldLoad, `worker with wrong mime '${mime}' should be blocked`); - let msg = error.message; - ok(msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/), - "should gets correct error message"); error.preventDefault(); resolve(); } @@ -74,9 +71,6 @@ function testWorkerImportScripts([mime, shouldLoad]) { }; worker.onerror = (error) => { ok(!shouldLoad, `worker/importScripts with wrong mime '${mime}' should be blocked`); - let msg = error.message; - ok(msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/), - "should gets correct error message"); error.preventDefault(); resolve(); } diff --git a/dom/storage/Storage.cpp b/dom/storage/Storage.cpp index 71fd932b7dc6..438acc56285f 100644 --- a/dom/storage/Storage.cpp +++ b/dom/storage/Storage.cpp @@ -14,6 +14,9 @@ #include "nsIPrincipal.h" #include "nsICookiePermission.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/StorageBinding.h" #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEventBinding.h" @@ -25,6 +28,9 @@ #include "nsServiceManagerUtils.h" namespace mozilla { + +using namespace ipc; + namespace dom { NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow) @@ -58,7 +64,6 @@ Storage::Storage(nsPIDOMWindowInner* aWindow, Storage::~Storage() { - mCache->KeepAlive(); } /* virtual */ JSObject* @@ -212,6 +217,30 @@ void Storage::BroadcastChangeNotification(const nsSubstring& aKey, const nsSubstring& aOldValue, const nsSubstring& aNewValue) +{ + if (!XRE_IsParentProcess() && GetType() == LocalStorage && mPrincipal) { + // If we are in a child process, we want to send a message to the parent in + // order to broadcast the StorageEvent correctly to any child process. + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + Unused << NS_WARN_IF(!cc->SendBroadcastLocalStorageChange( + mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue), + IPC::Principal(mPrincipal), mIsPrivate)); + } + + DispatchStorageEvent(GetType(), mDocumentURI, aKey, aOldValue, aNewValue, + mPrincipal, mIsPrivate, this, false); +} + +/* static */ void +Storage::DispatchStorageEvent(StorageType aStorageType, + const nsAString& aDocumentURI, + const nsAString& aKey, + const nsAString& aOldValue, + const nsAString& aNewValue, + nsIPrincipal* aPrincipal, + bool aIsPrivate, + Storage* aStorage, + bool aImmediateDispatch) { StorageEventInit dict; dict.mBubbles = false; @@ -219,21 +248,67 @@ Storage::BroadcastChangeNotification(const nsSubstring& aKey, dict.mKey = aKey; dict.mNewValue = aNewValue; dict.mOldValue = aOldValue; - dict.mStorageArea = this; - dict.mUrl = mDocumentURI; + dict.mStorageArea = aStorage; + dict.mUrl = aDocumentURI; // Note, this DOM event should never reach JS. It is cloned later in // nsGlobalWindow. RefPtr event = StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict); + event->SetPrincipal(aPrincipal); + RefPtr r = new StorageNotifierRunnable(event, - GetType() == LocalStorage + aStorageType == LocalStorage ? u"localStorage" : u"sessionStorage", - IsPrivate()); - NS_DispatchToMainThread(r); + aIsPrivate); + + if (aImmediateDispatch) { + Unused << r->Run(); + } else { + NS_DispatchToMainThread(r); + } + + // If we are in the parent process and we have the principal, we want to + // broadcast this event to every other process. + if (aStorageType == LocalStorage && XRE_IsParentProcess() && aPrincipal) { + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendDispatchLocalStorageChange( + nsString(aDocumentURI), nsString(aKey), nsString(aOldValue), + nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate); + } + } +} + +void +Storage::ApplyEvent(StorageEvent* aStorageEvent) +{ + MOZ_ASSERT(aStorageEvent); + + nsAutoString key; + nsAutoString old; + nsAutoString value; + + aStorageEvent->GetKey(key); + aStorageEvent->GetNewValue(value); + + // No key means clearing the full storage. + if (key.IsVoid()) { + MOZ_ASSERT(value.IsVoid()); + mCache->Clear(this, StorageCache::E10sPropagated); + return; + } + + // No new value means removing the key. + if (value.IsVoid()) { + mCache->RemoveItem(this, key, old, StorageCache::E10sPropagated); + return; + } + + // Otherwise, we set the new value. + mCache->SetItem(this, key, value, old, StorageCache::E10sPropagated); } static const char kPermissionType[] = "cookie"; diff --git a/dom/storage/Storage.h b/dom/storage/Storage.h index fb0c93966341..462c9b9877dd 100644 --- a/dom/storage/Storage.h +++ b/dom/storage/Storage.h @@ -24,6 +24,7 @@ namespace dom { class StorageManagerBase; class StorageCache; +class StorageEvent; class Storage final : public nsIDOMStorage @@ -129,6 +130,28 @@ public: return mCache == aOther->mCache; } + // aStorage can be null if this method is called by ContentChild. + // + // aImmediateDispatch is for use by (main-thread) IPC code so that PContent + // ordering can be maintained. Without this, the event would be enqueued and + // run in a future turn of the event loop, potentially allowing other PContent + // Recv* methods to trigger script that wants to assume our localstorage + // changes have already been applied. This is the case for message manager + // messages which are used by ContentTask testing logic and webextensions. + static void + DispatchStorageEvent(StorageType aStorageType, + const nsAString& aDocumentURI, + const nsAString& aKey, + const nsAString& aOldValue, + const nsAString& aNewValue, + nsIPrincipal* aPrincipal, + bool aIsPrivate, + Storage* aStorage, + bool aImmediateDispatch); + + void + ApplyEvent(StorageEvent* aStorageEvent); + protected: // The method checks whether the caller can use a storage. // CanUseStorage is called before any DOM initiated operation diff --git a/dom/storage/StorageCache.cpp b/dom/storage/StorageCache.cpp index 85da26bb4764..1d89cdfb4978 100644 --- a/dom/storage/StorageCache.cpp +++ b/dom/storage/StorageCache.cpp @@ -59,7 +59,7 @@ GetDataSetIndex(const Storage* aStorage) NS_IMPL_ADDREF(StorageCacheBridge) -// Since there is no consumer of return value of Release, we can turn this +// Since there is no consumer of return value of Release, we can turn this // method to void to make implementation of asynchronous StorageCache::Release // much simpler. NS_IMETHODIMP_(void) StorageCacheBridge::Release(void) @@ -149,7 +149,7 @@ StorageCache::Init(StorageManagerBase* aManager, // Check the quota string has (or has not) the identical origin suffix as // this storage cache is bound to. MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix)); - MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope, + MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope, NS_LITERAL_CSTRING("^"))); mUsage = aManager->GetOriginUsage(mQuotaOriginScope); @@ -198,28 +198,33 @@ StorageCache::DataSet(const Storage* aStorage) } bool -StorageCache::ProcessUsageDelta(const Storage* aStorage, int64_t aDelta) +StorageCache::ProcessUsageDelta(const Storage* aStorage, int64_t aDelta, + const MutationSource aSource) { - return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta); + return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource); } bool -StorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta) +StorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta, + const MutationSource aSource) { // Check if we are in a low disk space situation - if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) { + if (aSource == ContentMutation && + aDelta > 0 && mManager && mManager->IsLowDiskSpace()) { return false; } // Check limit per this origin Data& data = mData[aGetDataSetIndex]; uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta; - if (aDelta > 0 && newOriginUsage > StorageManagerBase::GetQuota()) { + if (aSource == ContentMutation && + aDelta > 0 && newOriginUsage > StorageManagerBase::GetQuota()) { return false; } // Now check eTLD+1 limit - if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) { + if (mUsage && + !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) { return false; } @@ -246,60 +251,6 @@ StorageCache::Preload() namespace { -// This class is passed to timer as a tick observer. It refers the cache -// and keeps it alive for a time. -class StorageCacheHolder : public nsITimerCallback -{ - virtual ~StorageCacheHolder() {} - - NS_DECL_ISUPPORTS - - NS_IMETHOD - Notify(nsITimer* aTimer) override - { - mCache = nullptr; - return NS_OK; - } - - RefPtr mCache; - -public: - explicit StorageCacheHolder(StorageCache* aCache) : mCache(aCache) {} -}; - -NS_IMPL_ISUPPORTS(StorageCacheHolder, nsITimerCallback) - -} // namespace - -void -StorageCache::KeepAlive() -{ - // Missing reference back to the manager means the cache is not responsible - // for its lifetime. Used for keeping sessionStorage live forever. - if (!mManager) { - return; - } - - if (!NS_IsMainThread()) { - // Timer and the holder must be initialized on the main thread. - NS_DispatchToMainThread(NewRunnableMethod(this, &StorageCache::KeepAlive)); - return; - } - - nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1"); - if (!timer) { - return; - } - - RefPtr holder = new StorageCacheHolder(this); - timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS, - nsITimer::TYPE_ONE_SHOT); - - mKeepAliveTimer.swap(timer); -} - -namespace { - // The AutoTimer provided by telemetry headers is only using static, // i.e. compile time known ID, but here we know the ID only at run time. // Hence a new class. @@ -439,7 +390,8 @@ StorageCache::GetItem(const Storage* aStorage, const nsAString& aKey, nsresult StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey, - const nsString& aValue, nsString& aOld) + const nsString& aValue, nsString& aOld, + const MutationSource aSource) { // Size of the cache that will change after this action. int64_t delta = 0; @@ -462,7 +414,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey, delta += static_cast(aValue.Length()) - static_cast(aOld.Length()); - if (!ProcessUsageDelta(aStorage, delta)) { + if (!ProcessUsageDelta(aStorage, delta, aSource)) { return NS_ERROR_DOM_QUOTA_REACHED; } @@ -472,7 +424,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey, data.mKeys.Put(aKey, aValue); - if (Persist(aStorage)) { + if (aSource == ContentMutation && Persist(aStorage)) { if (!sDatabase) { NS_ERROR("Writing to localStorage after the database has been shut down" ", data lose!"); @@ -491,7 +443,7 @@ StorageCache::SetItem(const Storage* aStorage, const nsAString& aKey, nsresult StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey, - nsString& aOld) + nsString& aOld, const MutationSource aSource) { if (Persist(aStorage)) { WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS); @@ -509,10 +461,10 @@ StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey, // Recalculate the cached data size const int64_t delta = -(static_cast(aOld.Length()) + static_cast(aKey.Length())); - Unused << ProcessUsageDelta(aStorage, delta); + Unused << ProcessUsageDelta(aStorage, delta, aSource); data.mKeys.Remove(aKey); - if (Persist(aStorage)) { + if (aSource == ContentMutation && Persist(aStorage)) { if (!sDatabase) { NS_ERROR("Writing to localStorage after the database has been shut down" ", data lose!"); @@ -526,7 +478,7 @@ StorageCache::RemoveItem(const Storage* aStorage, const nsAString& aKey, } nsresult -StorageCache::Clear(const Storage* aStorage) +StorageCache::Clear(const Storage* aStorage, const MutationSource aSource) { bool refresh = false; if (Persist(aStorage)) { @@ -548,11 +500,11 @@ StorageCache::Clear(const Storage* aStorage) bool hadData = !!data.mKeys.Count(); if (hadData) { - Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage); + Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource); data.mKeys.Clear(); } - if (Persist(aStorage) && (refresh || hadData)) { + if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) { if (!sDatabase) { NS_ERROR("Writing to localStorage after the database has been shut down" ", data lose!"); @@ -667,9 +619,6 @@ StorageCache::LoadItem(const nsAString& aKey, const nsString& aValue) void StorageCache::LoadDone(nsresult aRv) { - // Keep the preloaded cache alive for a time - KeepAlive(); - MonitorAutoLock monitor(mMonitor); mLoadResult = aRv; mLoaded = true; @@ -730,12 +679,13 @@ StorageUsage::LoadUsage(const int64_t aUsage) bool StorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, - const int64_t aDelta) + const int64_t aDelta, const StorageCache::MutationSource aSource) { MOZ_ASSERT(NS_IsMainThread()); int64_t newUsage = mUsage[aDataSetIndex] + aDelta; - if (aDelta > 0 && newUsage > StorageManagerBase::GetQuota()) { + if (aSource == StorageCache::ContentMutation && + aDelta > 0 && newUsage > StorageManagerBase::GetQuota()) { return false; } diff --git a/dom/storage/StorageCache.h b/dom/storage/StorageCache.h index dad9daf395f8..c8549b1e9dfc 100644 --- a/dom/storage/StorageCache.h +++ b/dom/storage/StorageCache.h @@ -78,8 +78,24 @@ class StorageCache : public StorageCacheBridge public: NS_IMETHOD_(void) Release(void); - // Note: We pass aOriginNoSuffix through the ctor here, because - // StorageCacheHashKey's ctor is creating this class and + enum MutationSource { + // The mutation is a result of an explicit JS mutation in this process. + // The mutation should be sent to the sDatabase. Quota will be checked and + // QuotaExceededError may be returned without the mutation being applied. + ContentMutation, + // The mutation initially was triggered in a different process and is being + // propagated to this cache via Storage::ApplyEvent. The mutation should + // not be sent to the sDatabase because the originating process is already + // doing that. (In addition to the redundant writes being wasteful, there + // is the potential for other processes to see inconsistent state from the + // database while preloading.) Quota will be updated but not checked + // because it's assumed it was checked in another process and data-coherency + // is more important than slightly exceeding quota. + E10sPropagated + }; + + // Note: We pass aOriginNoSuffix through the ctor here, because + // StorageCacheHashKey's ctor is creating this class and // accepts reversed-origin-no-suffix as an argument - the hashing key. explicit StorageCache(const nsACString* aOriginNoSuffix); @@ -109,10 +125,13 @@ public: nsresult GetItem(const Storage* aStorage, const nsAString& aKey, nsAString& aRetval); nsresult SetItem(const Storage* aStorage, const nsAString& aKey, - const nsString& aValue, nsString& aOld); + const nsString& aValue, nsString& aOld, + const MutationSource aSource=ContentMutation); nsresult RemoveItem(const Storage* aStorage, const nsAString& aKey, - nsString& aOld); - nsresult Clear(const Storage* aStorage); + nsString& aOld, + const MutationSource aSource=ContentMutation); + nsresult Clear(const Storage* aStorage, + const MutationSource aSource=ContentMutation); void GetKeys(const Storage* aStorage, nsTArray& aKeys); @@ -182,8 +201,18 @@ private: // Changes the quota usage on the given data set if it fits the quota. // If not, then false is returned and no change to the set must be done. - bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta); - bool ProcessUsageDelta(const Storage* aStorage, const int64_t aDelta); + // A special case is if aSource==E10sPropagated, then we will return true even + // if the change would put us over quota. This is done to ensure coherency of + // caches between processes in the face of races. It does allow an attacker + // to potentially use N multiples of the quota storage limit if they can + // arrange for their origin to execute code in N processes. However, this is + // not considered a particularly concerning threat model because it's already + // very possible for a rogue page to attempt to intentionally fill up the + // user's storage through the use of multiple domains. + bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta, + const MutationSource aSource=ContentMutation); + bool ProcessUsageDelta(const Storage* aStorage, const int64_t aDelta, + const MutationSource aSource=ContentMutation); private: // When a cache is reponsible for its life time (in case of localStorage data @@ -196,9 +225,6 @@ private: // Obtained from the manager during initialization (Init method). RefPtr mUsage; - // Timer that holds this cache alive for a while after it has been preloaded. - nsCOMPtr mKeepAliveTimer; - // Principal the cache has been initially created for, this is used only for // sessionStorage access checks since sessionStorage objects are strictly // scoped by a principal. localStorage objects on the other hand are scoped @@ -211,7 +237,7 @@ private: // The origin attributes suffix nsCString mOriginSuffix; - // The eTLD+1 scope used to count quota usage. It is in the reversed format + // The eTLD+1 scope used to count quota usage. It is in the reversed format // and contains the origin attributes suffix. nsCString mQuotaOriginScope; @@ -277,7 +303,8 @@ class StorageUsage : public StorageUsageBridge public: explicit StorageUsage(const nsACString& aOriginScope); - bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta); + bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta, + const StorageCache::MutationSource aSource); private: virtual const nsCString& OriginScope() { return mOriginScope; } diff --git a/dom/storage/StorageIPC.cpp b/dom/storage/StorageIPC.cpp index ac9e5f38ec6a..19abddacb90f 100644 --- a/dom/storage/StorageIPC.cpp +++ b/dom/storage/StorageIPC.cpp @@ -224,6 +224,12 @@ StorageDBChild::RecvObserve(const nsCString& aTopic, mozilla::ipc::IPCResult StorageDBChild::RecvOriginsHavingData(nsTArray&& aOrigins) { + // Force population of mOriginsHavingData even if there are no origins so that + // ShouldPreloadOrigin does not generate false positives for all origins. + if (!aOrigins.Length()) { + Unused << OriginsHavingData(); + } + for (uint32_t i = 0; i < aOrigins.Length(); ++i) { OriginsHavingData().PutEntry(aOrigins[i]); } diff --git a/dom/storage/StorageManager.cpp b/dom/storage/StorageManager.cpp index c0301996b79b..17f086eeb244 100644 --- a/dom/storage/StorageManager.cpp +++ b/dom/storage/StorageManager.cpp @@ -309,7 +309,7 @@ StorageManagerBase::DropCache(StorageCache* aCache) } nsresult -StorageManagerBase::GetStorageInternal(bool aCreate, +StorageManagerBase::GetStorageInternal(CreateMode aCreateMode, mozIDOMWindow* aWindow, nsIPrincipal* aPrincipal, const nsAString& aDocumentURI, @@ -331,12 +331,12 @@ StorageManagerBase::GetStorageInternal(bool aCreate, // Get or create a cache for the given scope if (!cache) { - if (!aCreate) { + if (aCreateMode == CreateMode::UseIfExistsNeverCreate) { *aRetval = nullptr; return NS_OK; } - if (!aRetval) { + if (aCreateMode == CreateMode::CreateIfShouldPreload) { // This is a demand to just preload the cache, if the scope has // no data stored, bypass creation and preload of the cache. StorageDBBridge* db = StorageCache::GetDatabase(); @@ -372,10 +372,11 @@ StorageManagerBase::GetStorageInternal(bool aCreate, } NS_IMETHODIMP -StorageManagerBase::PrecacheStorage(nsIPrincipal* aPrincipal) +StorageManagerBase::PrecacheStorage(nsIPrincipal* aPrincipal, + nsIDOMStorage** aRetval) { - return GetStorageInternal(true, nullptr, aPrincipal, EmptyString(), false, - nullptr); + return GetStorageInternal(CreateMode::CreateIfShouldPreload, nullptr, + aPrincipal, EmptyString(), false, aRetval); } NS_IMETHODIMP @@ -385,8 +386,8 @@ StorageManagerBase::CreateStorage(mozIDOMWindow* aWindow, bool aPrivate, nsIDOMStorage** aRetval) { - return GetStorageInternal(true, aWindow, aPrincipal, aDocumentURI, aPrivate, - aRetval); + return GetStorageInternal(CreateMode::CreateAlways, aWindow, aPrincipal, + aDocumentURI, aPrivate, aRetval); } NS_IMETHODIMP @@ -395,8 +396,8 @@ StorageManagerBase::GetStorage(mozIDOMWindow* aWindow, bool aPrivate, nsIDOMStorage** aRetval) { - return GetStorageInternal(false, aWindow, aPrincipal, EmptyString(), aPrivate, - aRetval); + return GetStorageInternal(CreateMode::UseIfExistsNeverCreate, aWindow, + aPrincipal, EmptyString(), aPrivate, aRetval); } NS_IMETHODIMP diff --git a/dom/storage/StorageManager.h b/dom/storage/StorageManager.h index 84ec05be7e95..247c7fc72049 100644 --- a/dom/storage/StorageManager.h +++ b/dom/storage/StorageManager.h @@ -90,8 +90,17 @@ private: const nsACString& aOriginNoSuffix, nsIPrincipal* aPrincipal); + enum class CreateMode { + // GetStorage: do not create if it's not already in memory. + UseIfExistsNeverCreate, + // CreateStorage: Create it if it's not already in memory. + CreateAlways, + // PrecacheStorage: Create only if the database says we ShouldPreloadOrigin. + CreateIfShouldPreload + }; + // Helper for creation of DOM storage objects - nsresult GetStorageInternal(bool aCreate, + nsresult GetStorageInternal(CreateMode aCreate, mozIDOMWindow* aWindow, nsIPrincipal* aPrincipal, const nsAString& aDocumentURI, diff --git a/dom/tests/browser/browser.ini b/dom/tests/browser/browser.ini index 80860cbf969b..e4f93551eee2 100644 --- a/dom/tests/browser/browser.ini +++ b/dom/tests/browser/browser.ini @@ -2,6 +2,7 @@ support-files = browser_frame_elements.html page_privatestorageevent.html + page_localstorage_e10s.html position.html test-console-api.html test_bug1004814.html @@ -40,6 +41,8 @@ skip-if = e10s skip-if = !e10s || os != "win" || processor != "x86" # Large-Allocation requires e10s [browser_largeAllocation_non_win32.js] skip-if = !e10s || (os == "win" && processor == "x86") # Large-Allocation requires e10s +[browser_localStorage_e10s.js] +skip-if = !e10s # This is a test of e10s functionality. [browser_localStorage_privatestorageevent.js] [browser_test__content.js] [browser_test_new_window_from_content.js] diff --git a/dom/tests/browser/browser_localStorage_e10s.js b/dom/tests/browser/browser_localStorage_e10s.js new file mode 100644 index 000000000000..ff431e27ab5d --- /dev/null +++ b/dom/tests/browser/browser_localStorage_e10s.js @@ -0,0 +1,330 @@ +const HELPER_PAGE_URL = + "http://example.com/browser/dom/tests/browser/page_localstorage_e10s.html"; +const HELPER_PAGE_ORIGIN = "http://example.com/"; + +// Simple tab wrapper abstracting our messaging mechanism; +class KnownTab { + constructor(name, tab) { + this.name = name; + this.tab = tab; + } + + cleanup() { + this.tab = null; + } +} + +// Simple data structure class to help us track opened tabs and their pids. +class KnownTabs { + constructor() { + this.byPid = new Map(); + this.byName = new Map(); + } + + cleanup() { + this.byPid = null; + this.byName = null; + } +} + +/** + * Open our helper page in a tab in its own content process, asserting that it + * really is in its own process. + */ +function* openTestTabInOwnProcess(name, knownTabs) { + let url = HELPER_PAGE_URL + '?' + encodeURIComponent(name); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let pid = tab.linkedBrowser.frameLoader.tabParent.osPid; + ok(!knownTabs.byName.has(name), "tab needs its own name: " + name); + ok(!knownTabs.byPid.has(pid), "tab needs to be in its own process: " + pid); + + let knownTab = new KnownTab(name, tab); + knownTabs.byPid.set(pid, knownTab); + knownTabs.byName.set(name, knownTab); + return knownTab; +} + +/** + * Close all the tabs we opened. + */ +function* cleanupTabs(knownTabs) { + for (let knownTab of knownTabs.byName.values()) { + yield BrowserTestUtils.removeTab(knownTab.tab); + knownTab.cleanup(); + } + knownTabs.cleanup(); +} + +/** + * Clear the origin's storage so that "OriginsHavingData" will return false for + * our origin. Note that this is only the case for AsyncClear() which is + * explicitly issued against a cache, or AsyncClearAll() which we can trigger + * by wiping all storage. However, the more targeted domain clearings that + * we can trigger via observer, AsyncClearMatchingOrigin and + * AsyncClearMatchingOriginAttributes will not clear the hashtable entry for + * the origin. + * + * So we explicitly access the cache here in the parent for the origin and issue + * an explicit clear. Clearing all storage might be a little easier but seems + * like asking for intermittent failures. + */ +function clearOriginStorageEnsuringNoPreload() { + let principal = + Services.scriptSecurityManager.createCodebasePrincipalFromOrigin( + HELPER_PAGE_ORIGIN); + // We want to use createStorage to force the cache to be created so we can + // issue the clear. It's possible for getStorage to return false but for the + // origin preload hash to still have our origin in it. + let storage = Services.domStorageManager.createStorage(null, principal, ""); + storage.clear(); + // We don't need to wait for anything. The clear call will have queued the + // clear operation on the database thread, and the child process requests + // for origins will likewise be answered via the database thread. +} + +function* verifyTabPreload(knownTab, expectStorageExists) { + let storageExists = yield ContentTask.spawn( + knownTab.tab.linkedBrowser, + HELPER_PAGE_ORIGIN, + function(origin) { + let principal = + Services.scriptSecurityManager.createCodebasePrincipalFromOrigin( + origin); + return !!Services.domStorageManager.getStorage(null, principal); + }); + is(storageExists, expectStorageExists, "Storage existence === preload"); +} + +/** + * Instruct the given tab to execute the given series of mutations. For + * simplicity, the mutations representation matches the expected events rep. + */ +function* mutateTabStorage(knownTab, mutations) { + yield ContentTask.spawn( + knownTab.tab.linkedBrowser, + { mutations }, + function(args) { + return content.wrappedJSObject.mutateStorage(args.mutations); + }); +} + +/** + * Instruct the given tab to add a "storage" event listener and record all + * received events. verifyTabStorageEvents is the corresponding method to + * check and assert the recorded events. + */ +function* recordTabStorageEvents(knownTab) { + yield ContentTask.spawn( + knownTab.tab.linkedBrowser, + {}, + function() { + return content.wrappedJSObject.listenForStorageEvents(); + }); +} + +/** + * Retrieve the current localStorage contents perceived by the tab and assert + * that they match the provided expected state. + */ +function* verifyTabStorageState(knownTab, expectedState) { + let actualState = yield ContentTask.spawn( + knownTab.tab.linkedBrowser, + {}, + function() { + return content.wrappedJSObject.getStorageState(); + }); + + for (let [expectedKey, expectedValue] of Object.entries(expectedState)) { + ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey); + is(actualState[expectedKey], expectedValue, "value correct"); + } + for (let actualKey of Object.keys(actualState)) { + if (!expectedState.hasOwnProperty(actualKey)) { + ok(false, "actual state has key it shouldn't have: " + actualKey); + } + } +} + +/** + * Retrieve and clear the storage events recorded by the tab and assert that + * they match the provided expected events. For simplicity, the expected events + * representation is the same as that used by mutateTabStorage. + */ +function* verifyTabStorageEvents(knownTab, expectedEvents) { + let actualEvents = yield ContentTask.spawn( + knownTab.tab.linkedBrowser, + {}, + function() { + return content.wrappedJSObject.returnAndClearStorageEvents(); + }); + + is(actualEvents.length, expectedEvents.length, "right number of events"); + for (let i = 0; i < actualEvents.length; i++) { + let [actualKey, actualNewValue, actualOldValue] = actualEvents[i]; + let [expectedKey, expectedNewValue, expectedOldValue] = expectedEvents[i]; + is(actualKey, expectedKey, "keys match"); + is(actualNewValue, expectedNewValue, "new values match"); + is(actualOldValue, expectedOldValue, "old values match"); + } +} + +// We spin up a ton of child processes. +requestLongerTimeout(4); + +/** + * Verify the basics of our multi-e10s localStorage support. We are focused on + * whitebox testing two things. When this is being written, broadcast filtering + * is not in place, but the test is intended to attempt to verify that its + * implementation does not break things. + * + * 1) That pages see the same localStorage state in a timely fashion when + * engaging in non-conflicting operations. We are not testing races or + * conflict resolution; the spec does not cover that. + * + * 2) That there are no edge-cases related to when the Storage instance is + * created for the page or the StorageCache for the origin. (StorageCache is + * what actually backs the Storage binding exposed to the page.) This + * matters because the following reasons can exist for them to be created: + * - Preload, on the basis of knowing the origin uses localStorage. The + * interesting edge case is when we have the same origin open in different + * processes and the origin starts using localStorage when it did not + * before. Preload will not have instantiated bindings, which could impact + * correctness. + * - The page accessing localStorage for read or write purposes. This is the + * obvious, boring one. + * - The page adding a "storage" listener. This is less obvious and + * interacts with the preload edge-case mentioned above. The page needs to + * hear "storage" events even if the page has not touched localStorage + * itself and its origin had nothing stored in localStorage when the page + * was created. + * + * We use the same simple child page in all tabs that: + * - can be instructed to listen for and record "storage" events + * - can be instructed to issue a series of localStorage writes + * - can be instructed to return the current entire localStorage contents + * + * We open the 5 following tabs: + * - Open a "writer" tab that does not listen for "storage" events and will + * issue only writes. + * - Open a "listener" tab instructed to listen for "storage" events + * immediately. We expect it to capture all events. + * - Open an "reader" tab that does not listen for "storage" events and will + * only issue reads when instructed. + * - Open a "lateWriteThenListen" tab that initially does nothing. We will + * later tell it to issue a write and then listen for events to make sure it + * captures the later events. + * - Open "lateOpenSeesPreload" tab after we've done everything and ensure that + * it preloads/precaches the data without us having touched localStorage or + * added an event listener. + */ +add_task(function*() { + // (There's already one about:blank page open and we open 5 new tabs, so 6 + // processes. Actually, 7, just in case.) + yield SpecialPowers.pushPrefEnv({ + set: [ + ["dom.ipc.processCount", 7] + ] + }); + + // Ensure that there is no localstorage data or potential false positives for + // localstorage preloads by forcing the origin to be cleared prior to the + // start of our test. + clearOriginStorageEnsuringNoPreload(); + + // - Open tabs. Don't configure any of them yet. + const knownTabs = new KnownTabs(); + const writerTab = yield* openTestTabInOwnProcess("writer", knownTabs); + const listenerTab = yield* openTestTabInOwnProcess("listener", knownTabs); + const readerTab = yield* openTestTabInOwnProcess("reader", knownTabs); + const lateWriteThenListenTab = yield* openTestTabInOwnProcess( + "lateWriteThenListen", knownTabs); + + // Sanity check that preloading did not occur in the tabs. + yield* verifyTabPreload(writerTab, false); + yield* verifyTabPreload(listenerTab, false); + yield* verifyTabPreload(readerTab, false); + + // - Configure the tabs. + yield* recordTabStorageEvents(listenerTab); + + // - Issue the initial batch of writes and verify. + const initialWriteMutations = [ + //[key (null=clear), newValue (null=delete), oldValue (verification)] + ["getsCleared", "1", null], + ["alsoGetsCleared", "2", null], + [null, null, null], + ["stays", "3", null], + ["clobbered", "pre", null], + ["getsDeletedLater", "4", null], + ["getsDeletedImmediately", "5", null], + ["getsDeletedImmediately", null, "5"], + ["alsoStays", "6", null], + ["getsDeletedLater", null, "4"], + ["clobbered", "post", "pre"] + ]; + const initialWriteState = { + stays: "3", + clobbered: "post", + alsoStays: "6" + }; + + yield* mutateTabStorage(writerTab, initialWriteMutations); + + yield* verifyTabStorageState(writerTab, initialWriteState); + yield* verifyTabStorageEvents(listenerTab, initialWriteMutations); + yield* verifyTabStorageState(listenerTab, initialWriteState); + yield* verifyTabStorageState(readerTab, initialWriteState); + + // - Issue second set of writes from lateWriteThenListen + const lateWriteMutations = [ + ["lateStays", "10", null], + ["lateClobbered", "latePre", null], + ["lateDeleted", "11", null], + ["lateClobbered", "lastPost", "latePre"], + ["lateDeleted", null, "11"] + ]; + const lateWriteState = Object.assign({}, initialWriteState, { + lateStays: "10", + lateClobbered: "lastPost" + }); + + yield* mutateTabStorage(lateWriteThenListenTab, lateWriteMutations); + yield* recordTabStorageEvents(lateWriteThenListenTab); + + yield* verifyTabStorageState(writerTab, lateWriteState); + yield* verifyTabStorageEvents(listenerTab, lateWriteMutations); + yield* verifyTabStorageState(listenerTab, lateWriteState); + yield* verifyTabStorageState(readerTab, lateWriteState); + + // - Issue last set of writes from writerTab. + const lastWriteMutations = [ + ["lastStays", "20", null], + ["lastDeleted", "21", null], + ["lastClobbered", "lastPre", null], + ["lastClobbered", "lastPost", "lastPre"], + ["lastDeleted", null, "21"] + ]; + const lastWriteState = Object.assign({}, lateWriteState, { + lastStays: "20", + lastClobbered: "lastPost" + }); + + yield* mutateTabStorage(writerTab, lastWriteMutations); + + yield* verifyTabStorageState(writerTab, lastWriteState); + yield* verifyTabStorageEvents(listenerTab, lastWriteMutations); + yield* verifyTabStorageState(listenerTab, lastWriteState); + yield* verifyTabStorageState(readerTab, lastWriteState); + yield* verifyTabStorageEvents(lateWriteThenListenTab, lastWriteMutations); + yield* verifyTabStorageState(lateWriteThenListenTab, lastWriteState); + + // - Open a fresh tab and make sure it sees the precache/preload + const lateOpenSeesPreload = + yield* openTestTabInOwnProcess("lateOpenSeesPreload", knownTabs); + yield* verifyTabPreload(lateOpenSeesPreload, true); + + // - Clean up. + yield* cleanupTabs(knownTabs); + + clearOriginStorageEnsuringNoPreload(); +}); diff --git a/dom/tests/browser/page_localstorage_e10s.html b/dom/tests/browser/page_localstorage_e10s.html new file mode 100644 index 000000000000..88025dfc931a --- /dev/null +++ b/dom/tests/browser/page_localstorage_e10s.html @@ -0,0 +1,56 @@ + + + + + + +

+ diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index 94091b209bfe..f0adbf2230ce 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -1557,19 +1557,27 @@ void CacheCreator::ResolvedCallback(JSContext* aCx, JS::Handle aValue) { AssertIsOnMainThread(); - MOZ_ASSERT(aValue.isObject()); + + if (!aValue.isObject()) { + FailLoaders(NS_ERROR_FAILURE); + return; + } JS::Rooted obj(aCx, &aValue.toObject()); Cache* cache = nullptr; nsresult rv = UNWRAP_OBJECT(Cache, obj, cache); - MOZ_ALWAYS_SUCCEEDS(rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailLoaders(NS_ERROR_FAILURE); + return; + } mCache = cache; - MOZ_ASSERT(mCache); + MOZ_DIAGNOSTIC_ASSERT(mCache); // If the worker is canceled, CancelMainThread() will have cleared the - // loaders. + // loaders via DeleteCache(). for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) { + MOZ_DIAGNOSTIC_ASSERT(mLoaders[i]); mLoaders[i]->Load(cache); } } @@ -1581,19 +1589,16 @@ CacheCreator::DeleteCache() // This is called when the load is canceled which can occur before // mCacheStorage is initialized. - if (!mCacheStorage) { - return; + if (mCacheStorage) { + // It's safe to do this while Cache::Match() and Cache::Put() calls are + // running. + IgnoredErrorResult rv; + RefPtr promise = mCacheStorage->Delete(mCacheName, rv); + + // We don't care to know the result of the promise object. } - // It's safe to do this while Cache::Match() and Cache::Put() calls are - // running. - IgnoredErrorResult rv; - RefPtr promise = mCacheStorage->Delete(mCacheName, rv); - if (NS_WARN_IF(rv.Failed())) { - return; - } - - // We don't care to know the result of the promise object. + // Always call this here to ensure the loaders array is cleared. FailLoaders(NS_ERROR_FAILURE); } diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 976defec2bc2..c0aec9f0690f 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -494,6 +494,82 @@ private: } }; +class ReportCompileErrorRunnable final : public WorkerRunnable +{ +public: + static void + CreateAndDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr runnable = + new ReportCompileErrorRunnable(aCx, aWorkerPrivate); + runnable->Dispatch(); + } + +private: + ReportCompileErrorRunnable(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) + { + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + void + PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override + { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // Dispatch may fail if the worker was canceled, no need to report that as + // an error, so don't call base class PostDispatch. + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + if (aWorkerPrivate->IsFrozen() || + aWorkerPrivate->IsParentWindowPaused()) { + MOZ_ASSERT(!IsDebuggerRunnable()); + aWorkerPrivate->QueueRunnable(this); + return true; + } + + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, EmptyString(), + EmptyString(), + EmptyString(), 0, 0, + JSREPORT_ERROR, + /* isErrorEvent */ false); + return true; + } + + if (aWorkerPrivate->IsServiceWorker()) { + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(), + aWorkerPrivate->WorkerName(), + aWorkerPrivate->ScriptURL(), + EmptyString(), EmptyString(), EmptyString(), + 0, 0, JSREPORT_ERROR, JSEXN_ERR); + } + return true; + } + + if (!aWorkerPrivate->IsAcceptingEvents()) { + return true; + } + + RefPtr event = + Event::Constructor(aWorkerPrivate, NS_LITERAL_STRING("error"), + EventInit()); + event->SetTrusted(true); + + nsEventStatus status = nsEventStatus_eIgnore; + aWorkerPrivate->DispatchDOMEvent(nullptr, event, nullptr, &status); + return true; + } +}; + class CompileScriptRunnable final : public WorkerRunnable { nsString mScriptURL; @@ -537,9 +613,15 @@ private: } // Make sure to propagate exceptions from rv onto aCx, so that they will get - // reported after we return. We do this for all failures on rv, because now - // we're using rv to track all the state we care about. - // + // reported after we return. We want to propagate just JS exceptions, + // because all the other errors are handled when the script is loaded. + // See: https://dom.spec.whatwg.org/#concept-event-fire + if (rv.Failed() && !rv.IsJSException()) { + ReportCompileErrorRunnable::CreateAndDispatch(aCx, aWorkerPrivate); + rv.SuppressException(); + return false; + } + // This is a little dumb, but aCx is in the null compartment here because we // set it up that way in our Run(), since we had not created the global at // that point yet. So we need to enter the compartment of our global, @@ -1117,7 +1199,8 @@ private: if (aWorkerPrivate->IsSharedWorker()) { aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename, mLine, mLineNumber, - mColumnNumber, mFlags); + mColumnNumber, mFlags, + /* isErrorEvent */ true); return true; } @@ -3291,7 +3374,8 @@ WorkerPrivateParent::BroadcastErrorToSharedWorkers( const nsAString& aLine, uint32_t aLineNumber, uint32_t aColumnNumber, - uint32_t aFlags) + uint32_t aFlags, + bool aIsErrorEvent) { AssertIsOnMainThread(); @@ -3322,31 +3406,42 @@ WorkerPrivateParent::BroadcastErrorToSharedWorkers( // May be null. nsPIDOMWindowInner* window = sharedWorker->GetOwner(); - RootedDictionary errorInit(aCx); - errorInit.mBubbles = false; - errorInit.mCancelable = true; - errorInit.mMessage = aMessage; - errorInit.mFilename = aFilename; - errorInit.mLineno = aLineNumber; - errorInit.mColno = aColumnNumber; + RefPtr event; - RefPtr errorEvent = - ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"), - errorInit); - if (!errorEvent) { + if (aIsErrorEvent) { + RootedDictionary errorInit(aCx); + errorInit.mBubbles = false; + errorInit.mCancelable = true; + errorInit.mMessage = aMessage; + errorInit.mFilename = aFilename; + errorInit.mLineno = aLineNumber; + errorInit.mColno = aColumnNumber; + + event = ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"), + errorInit); + } else { + event = Event::Constructor(sharedWorker, NS_LITERAL_STRING("error"), + EventInit()); + } + + if (!event) { ThrowAndReport(window, NS_ERROR_UNEXPECTED); continue; } - errorEvent->SetTrusted(true); + event->SetTrusted(true); bool defaultActionEnabled; - nsresult rv = sharedWorker->DispatchEvent(errorEvent, &defaultActionEnabled); + nsresult rv = sharedWorker->DispatchEvent(event, &defaultActionEnabled); if (NS_FAILED(rv)) { ThrowAndReport(window, rv); continue; } + if (!aIsErrorEvent) { + continue; + } + if (defaultActionEnabled) { // Add the owning window to our list so that we will fire an error event // at it later. diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 3caeee83268b..2f00cee71c98 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -403,7 +403,8 @@ public: const nsAString& aLine, uint32_t aLineNumber, uint32_t aColumnNumber, - uint32_t aFlags); + uint32_t aFlags, + bool aIsErrorEvent); void WorkerScriptLoaded(); diff --git a/dom/workers/test/test_404.html b/dom/workers/test/test_404.html index e2e83a35e308..109f14bf0f85 100644 --- a/dom/workers/test/test_404.html +++ b/dom/workers/test/test_404.html @@ -25,7 +25,6 @@ Tests of DOM Worker Threads worker.onerror = function(event) { is(event.target, worker); - is(event.message, 'NetworkError: Failed to load worker script at "nonexistent_worker.js"'); event.preventDefault(); SimpleTest.finish(); }; @@ -38,4 +37,3 @@ Tests of DOM Worker Threads - diff --git a/dom/workers/test/test_bug1036484.html b/dom/workers/test/test_bug1036484.html index 17b9d490fe69..49c31bbc9e72 100644 --- a/dom/workers/test/test_bug1036484.html +++ b/dom/workers/test/test_bug1036484.html @@ -25,7 +25,6 @@ function test(script) { worker.onerror = function(event) { is(event.target, worker); - ok(event.message.startsWith("NetworkError: Failed to load worker script")) event.preventDefault(); runTests(); }; diff --git a/dom/workers/test/test_loadError.html b/dom/workers/test/test_loadError.html index dc109b796b0e..b9a215d11e79 100644 --- a/dom/workers/test/test_loadError.html +++ b/dom/workers/test/test_loadError.html @@ -13,15 +13,13 @@