/* -*- 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 "nsGlobalWindow.h" #include #include "mozilla/MemoryReporting.h" // Local Includes #include "Navigator.h" #include "nsScreen.h" #include "nsHistory.h" #include "nsPerformance.h" #include "nsDOMNavigationTiming.h" #include "nsIDOMStorageManager.h" #include "mozilla/dom/DOMStorage.h" #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEventBinding.h" #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) #include "mozilla/dom/WindowOrientationObserver.h" #endif #include "nsDOMOfflineResourceList.h" #include "nsError.h" #include "nsIIdleService.h" #include "nsISizeOfEventTarget.h" #include "nsDOMJSUtils.h" #include "nsArrayUtils.h" #include "nsIDOMWindowCollection.h" #include "nsDOMWindowList.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "nsIDocShellTreeOwner.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPermissionManager.h" #include "nsIScriptContext.h" #include "nsIScriptTimeoutHandler.h" #include "nsIController.h" #include "nsScriptNameSpaceManager.h" #include "nsISlowScriptDebug.h" #include "nsWindowMemoryReporter.h" #include "WindowNamedPropertiesHandler.h" #include "nsFrameSelection.h" #include "nsNetUtil.h" #include "nsVariant.h" // Helper Classes #include "nsJSUtils.h" #include "jsapi.h" // for JSAutoRequest #include "jswrapper.h" #include "nsCharSeparatedTokenizer.h" #include "nsReadableUtils.h" #include "nsDOMClassInfo.h" #include "nsJSEnvironment.h" #include "ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/Likely.h" #include "mozilla/Snprintf.h" #include "mozilla/unused.h" // Other Classes #include "mozilla/dom/BarProps.h" #include "nsContentCID.h" #include "nsLayoutStatics.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/workers/Workers.h" #include "mozilla/dom/ToJSValue.h" #include "nsJSPrincipals.h" #include "mozilla/Attributes.h" #include "mozilla/Debug.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStates.h" #include "mozilla/MouseEvents.h" #include "mozilla/ProcessHangMonitor.h" #include "AudioChannelService.h" #include "nsAboutProtocolUtils.h" #include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE #include "PostMessageEvent.h" // Interfaces Needed #include "nsIFrame.h" #include "nsCanvasFrame.h" #include "nsIWidget.h" #include "nsIWidgetListener.h" #include "nsIBaseWindow.h" #include "nsIDeviceSensors.h" #include "nsIContent.h" #include "nsIDocShell.h" #include "nsIDocCharset.h" #include "nsIDocument.h" #include "Crypto.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMEvent.h" #include "nsIDOMOfflineResourceList.h" #include "nsDOMString.h" #include "nsIEmbeddingSiteWindow.h" #include "nsThreadUtils.h" #include "nsILoadContext.h" #include "nsIPresShell.h" #include "nsIScrollableFrame.h" #include "nsView.h" #include "nsViewManager.h" #include "nsISelectionController.h" #include "nsISelection.h" #include "nsIPrompt.h" #include "nsIPromptService.h" #include "nsIPromptFactory.h" #include "nsIWritablePropertyBag2.h" #include "nsIWebNavigation.h" #include "nsIWebBrowserChrome.h" #include "nsIWebBrowserFind.h" // For window.find() #include "nsIWindowMediator.h" // For window.find() #include "nsComputedDOMStyle.h" #include "nsIEntropyCollector.h" #include "nsDOMCID.h" #include "nsDOMWindowUtils.h" #include "nsIWindowWatcher.h" #include "nsPIWindowWatcher.h" #include "nsIContentViewer.h" #include "nsIScriptError.h" #include "nsIControllers.h" #include "nsIControllerContext.h" #include "nsGlobalWindowCommands.h" #include "nsAutoPtr.h" #include "nsQueryObject.h" #include "nsContentUtils.h" #include "nsCSSProps.h" #include "nsIDOMFileList.h" #include "nsIURIFixup.h" #ifndef DEBUG #include "nsIAppStartup.h" #include "nsToolkitCompsCID.h" #endif #include "nsCDefaultURIFixup.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "nsIObserverService.h" #include "nsFocusManager.h" #include "nsIXULWindow.h" #include "nsITimedChannel.h" #include "nsServiceManagerUtils.h" #ifdef MOZ_XUL #include "nsIDOMXULControlElement.h" #include "nsMenuPopupFrame.h" #endif #include "mozilla/dom/CustomEvent.h" #include "nsIJARChannel.h" #include "xpcprivate.h" #ifdef NS_PRINTING #include "nsIPrintSettings.h" #include "nsIPrintSettingsService.h" #include "nsIWebBrowserPrint.h" #endif #include "nsWindowRoot.h" #include "nsNetCID.h" #include "nsIArray.h" // XXX An unfortunate dependency exists here (two XUL files). #include "nsIDOMXULDocument.h" #include "nsIDOMXULCommandDispatcher.h" #include "nsBindingManager.h" #include "nsXBLService.h" // used for popup blocking, needs to be converted to something // belonging to the back-end like nsIContentPolicy #include "nsIPopupWindowManager.h" #include "nsIDragService.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Selection.h" #include "nsFrameLoader.h" #include "nsISupportsPrimitives.h" #include "nsXPCOMCID.h" #include "mozilla/Logging.h" #include "prenv.h" #include "prprf.h" #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/indexedDB/IDBFactory.h" #include "mozilla/dom/Promise.h" #ifdef MOZ_GAMEPAD #include "mozilla/dom/Gamepad.h" #include "mozilla/dom/GamepadService.h" #endif #include "mozilla/dom/VRDevice.h" #include "nsRefreshDriver.h" #include "Layers.h" #include "mozilla/AddonPathService.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "nsLocation.h" #include "nsHTMLDocument.h" #include "nsWrapperCacheInlines.h" #include "mozilla/DOMEventTargetHelper.h" #include "prrng.h" #include "nsSandboxFlags.h" #include "TimeChangeObserver.h" #include "TouchCaret.h" #include "mozilla/dom/AudioContext.h" #include "mozilla/dom/BrowserElementDictionariesBinding.h" #include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/Console.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/HashChangeEvent.h" #include "mozilla/dom/MozSelfSupportBinding.h" #include "mozilla/dom/PopStateEvent.h" #include "mozilla/dom/PopupBlockedEvent.h" #include "mozilla/dom/PrimitiveConversions.h" #include "mozilla/dom/WindowBinding.h" #include "nsITabChild.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ServiceWorkerRegistration.h" #ifdef HAVE_SIDEBAR #include "mozilla/dom/ExternalBinding.h" #endif #ifdef MOZ_WEBSPEECH #include "mozilla/dom/SpeechSynthesis.h" #endif #ifdef MOZ_B2G #include "nsPISocketTransportService.h" #endif // Apple system headers seem to have a check() macro. #ifdef check class nsIScriptTimeoutHandler; #undef check #endif // check #include "AccessCheck.h" #ifdef ANDROID #include #endif #ifdef XP_WIN #include #define getpid _getpid #else #include // for getpid() #endif #include "mozilla/dom/IdleDeadline.h" // issue 463 // Caches for Mach factor monitoring (TenFourFox issue 463). // These can be singletons since they're shared over the entire machine. // We are stricter with the minimum than with decode delay (issue 434), // which also uses Mach factor analysis to determine load, because most // calls will have a max timeout and thus the function is likely to run at // *some* point. static const int32_t MACH_FACTOR_MIN = 900; static const int32_t MACH_CHECK_INTERVAL = 1000; // This number is actually in the W3C standard, but we allow it to be // adjusted. static const int32_t CALLBACK_IDLE_INTERVAL = 50; static int32_t sMachFactorMin = MACH_FACTOR_MIN; static int32_t sIdleCallbackMachCheckInterval = MACH_CHECK_INTERVAL; static int32_t sIdleCallbackIdleInterval = CALLBACK_IDLE_INTERVAL; #include #include #include #include #include static processor_set_name_port_t sMachDefaultPset; static uint32_t sNumCPUs; static struct processor_set_load_info sMachLoadInfo; static host_name_port_t sMachHost; static const char kStorageEnabled[] = "dom.storage.enabled"; using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::ipc; using mozilla::BasePrincipal; using mozilla::PrincipalOriginAttributes; using mozilla::TimeStamp; using mozilla::TimeDuration; using mozilla::dom::cache::CacheStorage; using mozilla::dom::indexedDB::IDBFactory; static LazyLogModule gDOMLeakPRLog("DOMLeak"); nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr; bool nsGlobalWindow::sWarnedAboutWindowInternal = false; bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false; static nsIEntropyCollector *gEntropyCollector = nullptr; static int32_t gRefCnt = 0; static int32_t gOpenPopupSpamCount = 0; static PopupControlState gPopupControlState = openAbused; static int32_t gRunningTimeoutDepth = 0; static bool gMouseDown = false; static bool gDragServiceDisabled = false; static FILE *gDumpFile = nullptr; static uint32_t gSerialCounter = 0; static uint32_t gTimeoutsRecentlySet = 0; static TimeStamp gLastRecordedRecentTimeouts; #define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC) #ifdef DEBUG_jst int32_t gTimeoutCnt = 0; #endif #if defined(DEBUG_bryner) || defined(DEBUG_chb) #define DEBUG_PAGE_CACHE #endif #define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added" // The default shortest interval/timeout we permit #define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms #define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms static int32_t gMinTimeoutValue; static int32_t gMinBackgroundTimeoutValue; inline int32_t nsGlobalWindow::DOMMinTimeoutValue() const { bool isBackground = !mOuterWindow || mOuterWindow->IsBackground(); return std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0); } // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit // uses 5. #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5 // The longest interval (as PRIntervalTime) we permit, or that our // timer code can handle, really. See DELAY_INTERVAL_LIMIT in // nsTimerImpl.h for details. #define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT #define FORWARD_TO_OUTER(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsInnerWindow()) { \ nsGlobalWindow *outer = GetOuterWindowInternal(); \ if (!HasActiveDocument()) { \ NS_WARNING(outer ? \ "Inner window does not have active document." : \ "No outer window available!"); \ return err_rval; \ } \ return outer->method args; \ } \ PR_END_MACRO #define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \ PR_BEGIN_MACRO \ MOZ_RELEASE_ASSERT(IsInnerWindow()); \ nsGlobalWindow *outer = GetOuterWindowInternal(); \ if (MOZ_LIKELY(HasActiveDocument())) { \ return outer->method args; \ } \ if (!outer) { \ NS_WARNING("No outer window available!"); \ errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \ } else { \ errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \ } \ return err_rval; \ PR_END_MACRO #define FORWARD_TO_OUTER_VOID(method, args) \ PR_BEGIN_MACRO \ if (IsInnerWindow()) { \ nsGlobalWindow *outer = GetOuterWindowInternal(); \ if (!HasActiveDocument()) { \ NS_WARNING(outer ? \ "Inner window does not have active document." : \ "No outer window available!"); \ return; \ } \ outer->method args; \ return; \ } \ PR_END_MACRO #define FORWARD_TO_OUTER_CHROME(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsInnerWindow()) { \ nsGlobalWindow *outer = GetOuterWindowInternal(); \ if (!HasActiveDocument()) { \ NS_WARNING(outer ? \ "Inner window does not have active document." : \ "No outer window available!"); \ return err_rval; \ } \ return ((nsGlobalChromeWindow *)outer)->method args; \ } \ PR_END_MACRO #define FORWARD_TO_INNER_CHROME(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsOuterWindow()) { \ if (!mInnerWindow) { \ NS_WARNING("No inner window available!"); \ return err_rval; \ } \ return ((nsGlobalChromeWindow *)mInnerWindow)->method args; \ } \ PR_END_MACRO #define FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsInnerWindow()) { \ nsGlobalWindow *outer = GetOuterWindowInternal(); \ if (!HasActiveDocument()) { \ NS_WARNING(outer ? \ "Inner window does not have active document." : \ "No outer window available!"); \ return err_rval; \ } \ return ((nsGlobalModalWindow *)outer)->method args; \ } \ PR_END_MACRO #define FORWARD_TO_INNER(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsOuterWindow()) { \ if (!mInnerWindow) { \ NS_WARNING("No inner window available!"); \ return err_rval; \ } \ return GetCurrentInnerWindowInternal()->method args; \ } \ PR_END_MACRO #define FORWARD_TO_INNER_MODAL_CONTENT_WINDOW(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsOuterWindow()) { \ if (!mInnerWindow) { \ NS_WARNING("No inner window available!"); \ return err_rval; \ } \ return ((nsGlobalModalWindow*)GetCurrentInnerWindowInternal())->method args; \ } \ PR_END_MACRO #define FORWARD_TO_INNER_VOID(method, args) \ PR_BEGIN_MACRO \ if (IsOuterWindow()) { \ if (!mInnerWindow) { \ NS_WARNING("No inner window available!"); \ return; \ } \ GetCurrentInnerWindowInternal()->method args; \ return; \ } \ PR_END_MACRO // Same as FORWARD_TO_INNER, but this will create a fresh inner if an // inner doesn't already exists. #define FORWARD_TO_INNER_CREATE(method, args, err_rval) \ PR_BEGIN_MACRO \ if (IsOuterWindow()) { \ if (!mInnerWindow) { \ if (mIsClosed) { \ return err_rval; \ } \ nsCOMPtr doc = GetDoc(); \ Unused << doc; \ if (!mInnerWindow) { \ return err_rval; \ } \ } \ return GetCurrentInnerWindowInternal()->method args; \ } \ PR_END_MACRO // CIDs static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); static const char sPopStatePrefStr[] = "browser.history.allowPopState"; #define NETWORK_UPLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkupload") #define NETWORK_DOWNLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkdownload") /** * An indirect observer object that means we don't have to implement nsIObserver * on nsGlobalWindow, where any script could see it. */ class nsGlobalWindowObserver final : public nsIObserver, public nsIInterfaceRequestor { public: explicit nsGlobalWindowObserver(nsGlobalWindow* aWindow) : mWindow(aWindow) {} NS_DECL_ISUPPORTS NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override { if (!mWindow) return NS_OK; return mWindow->Observe(aSubject, aTopic, aData); } void Forget() { mWindow = nullptr; } NS_IMETHODIMP GetInterface(const nsIID& aIID, void** aResult) override { if (mWindow && aIID.Equals(NS_GET_IID(nsIDOMWindow)) && mWindow) { return mWindow->QueryInterface(aIID, aResult); } return NS_NOINTERFACE; } private: ~nsGlobalWindowObserver() {} // This reference is non-owning and safe because it's cleared by // nsGlobalWindow::CleanUp(). nsGlobalWindow* MOZ_NON_OWNING_REF mWindow; }; NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor) nsTimeout::nsTimeout() : mCleared(false), mRunning(false), mIsInterval(false), mPublicId(0), mInterval(0), mFiringDepth(0), mNestingLevel(0), mPopupState(openAllowed) { #ifdef DEBUG_jst { extern int gTimeoutCnt; ++gTimeoutCnt; } #endif MOZ_COUNT_CTOR(nsTimeout); } nsTimeout::~nsTimeout() { #ifdef DEBUG_jst { extern int gTimeoutCnt; --gTimeoutCnt; } #endif if (mTimer) { mTimer->Cancel(); mTimer = nullptr; } MOZ_COUNT_DTOR(nsTimeout); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsTimeout) NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsTimeout) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTimeout) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTimeout, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTimeout, Release) nsresult nsTimeout::InitTimer(uint32_t aDelay) { return mTimer->InitWithNameableFuncCallback( nsGlobalWindow::TimerCallback, this, aDelay, nsITimer::TYPE_ONE_SHOT, nsGlobalWindow::TimerNameCallback); } // Return true if this timeout has a refcount of 1. This is used to check // that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout. bool nsTimeout::HasRefCntOne() { return mRefCnt.get() == 1; } static already_AddRefed CreateVoidVariant() { RefPtr writable = new nsVariantCC(); writable->SetAsVoid(); return writable.forget(); } nsresult DialogValueHolder::Get(nsIPrincipal* aSubject, nsIVariant** aResult) { nsCOMPtr result; if (aSubject->SubsumesConsideringDomain(mOrigin)) { result = mValue; } else { result = CreateVoidVariant(); } result.forget(aResult); return NS_OK; } void DialogValueHolder::Get(JSContext* aCx, JS::Handle aScope, nsIPrincipal* aSubject, JS::MutableHandle aResult, mozilla::ErrorResult& aError) { if (aSubject->Subsumes(mOrigin)) { aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope, mValue, aResult); } else { aResult.setUndefined(); } } namespace mozilla { namespace dom { extern uint64_t NextWindowID(); } // namespace dom } // namespace mozilla nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindow *aOuterWindow) : mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0), mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false), mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr), mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false), mMayHaveMouseEnterLeaveEventListener(false), mMayHavePointerEnterLeaveEventListener(false), mIsModalContentWindow(false), mIsActive(false), mIsBackground(false), mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false), mDesktopModeViewport(false), mInnerWindow(nullptr), mOuterWindow(aOuterWindow), // Make sure no actual window ends up with mWindowID == 0 mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false), mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false) {} nsPIDOMWindow::~nsPIDOMWindow() {} // DialogValueHolder CC goop. NS_IMPL_CYCLE_COLLECTION(DialogValueHolder, mValue) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(DialogValueHolder) NS_IMPL_CYCLE_COLLECTING_RELEASE(DialogValueHolder) //***************************************************************************** // nsOuterWindowProxy: Outer Window Proxy //***************************************************************************** class nsOuterWindowProxy : public js::Wrapper { public: MOZ_CONSTEXPR nsOuterWindowProxy() : js::Wrapper(0) { } virtual bool finalizeInBackground(JS::Value priv) const override { return false; } // Standard internal methods virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const override; virtual bool defineProperty(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle desc, JS::ObjectOpResult &result) const override; virtual bool ownPropertyKeys(JSContext *cx, JS::Handle proxy, JS::AutoIdVector &props) const override; virtual bool delete_(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::ObjectOpResult &result) const override; virtual bool enumerate(JSContext *cx, JS::Handle proxy, JS::MutableHandle vp) const override; virtual bool preventExtensions(JSContext* cx, JS::Handle proxy, JS::ObjectOpResult& result) const override; virtual bool isExtensible(JSContext *cx, JS::Handle proxy, bool *extensible) const override; virtual bool has(JSContext *cx, JS::Handle proxy, JS::Handle id, bool *bp) const override; virtual bool get(JSContext *cx, JS::Handle proxy, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) const override; virtual bool set(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult &result) const override; // SpiderMonkey extensions virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const override; virtual bool hasOwn(JSContext *cx, JS::Handle proxy, JS::Handle id, bool *bp) const override; virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle proxy, JS::AutoIdVector &props) const override; virtual const char *className(JSContext *cx, JS::Handle wrapper) const override; virtual void finalize(JSFreeOp *fop, JSObject *proxy) const override; virtual bool isCallable(JSObject *obj) const override { return false; } virtual bool isConstructor(JSObject *obj) const override { return false; } virtual bool watch(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::Handle callable) const override; virtual bool unwatch(JSContext *cx, JS::Handle proxy, JS::Handle id) const override; static void ObjectMoved(JSObject *obj, const JSObject *old); static const nsOuterWindowProxy singleton; protected: static nsGlobalWindow* GetOuterWindow(JSObject *proxy) { nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports( static_cast(js::GetProxyExtra(proxy, 0).toPrivate())); MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow()); return outerWindow; } // False return value means we threw an exception. True return value // but false "found" means we didn't have a subframe at that index. bool GetSubframeWindow(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle vp, bool &found) const; // Returns a non-null window only if id is an index and we have a // window at that index. already_AddRefed GetSubframeWindow(JSContext *cx, JS::Handle proxy, JS::Handle id) const; bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, JS::AutoIdVector &props) const; }; const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT( "Proxy", 0, /* additional class flags */ PROXY_MAKE_EXT( false, /* isWrappedNative */ nsOuterWindowProxy::ObjectMoved )); const char * nsOuterWindowProxy::className(JSContext *cx, JS::Handle proxy) const { MOZ_ASSERT(js::IsProxy(proxy)); return "Window"; } void nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy) const { nsGlobalWindow* outerWindow = GetOuterWindow(proxy); if (outerWindow) { outerWindow->ClearWrapper(); // Ideally we would use OnFinalize here, but it's possible that // EnsureScriptEnvironment will later be called on the window, and we don't // want to create a new script object in that case. Therefore, we need to // write a non-null value that will reliably crash when dereferenced. outerWindow->PoisonOuterWindowProxy(proxy); } } bool nsOuterWindowProxy::getPropertyDescriptor(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const { // The only thing we can do differently from js::Wrapper is shadow stuff with // our indexed properties, so we can just try getOwnPropertyDescriptor and if // that gives us nothing call on through to js::Wrapper. desc.object().set(nullptr); if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) { return false; } if (desc.object()) { return true; } return js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc); } bool nsOuterWindowProxy::getOwnPropertyDescriptor(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const { bool found; if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) { return false; } if (found) { FillPropertyDescriptor(desc, proxy, true); return true; } // else fall through to js::Wrapper return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); } bool nsOuterWindowProxy::defineProperty(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle desc, JS::ObjectOpResult &result) const { int32_t index = GetArrayIndexFromId(cx, id); if (IsArrayIndex(index)) { // Spec says to Reject whether this is a supported index or not, // since we have no indexed setter or indexed creator. It is up // to the caller to decide whether to throw a TypeError. return result.failCantDefineWindowElement(); } return js::Wrapper::defineProperty(cx, proxy, id, desc, result); } bool nsOuterWindowProxy::ownPropertyKeys(JSContext *cx, JS::Handle proxy, JS::AutoIdVector &props) const { // Just our indexed stuff followed by our "normal" own property names. if (!AppendIndexedPropertyNames(cx, proxy, props)) { return false; } JS::AutoIdVector innerProps(cx); if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) { return false; } return js::AppendUnique(cx, props, innerProps); } bool nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::ObjectOpResult &result) const { if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { // Fail (which means throw if strict, else return false). return result.failCantDeleteWindowElement(); } int32_t index = GetArrayIndexFromId(cx, id); if (IsArrayIndex(index)) { // Indexed, but not supported. Spec says return true. return result.succeed(); } return js::Wrapper::delete_(cx, proxy, id, result); } bool nsOuterWindowProxy::preventExtensions(JSContext* cx, JS::Handle proxy, JS::ObjectOpResult& result) const { // If [[Extensible]] could be false, then navigating a window could navigate // to a window that's [[Extensible]] after being at one that wasn't: an // invariant violation. So never change a window's extensibility. return result.failCantPreventExtensions(); } bool nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle proxy, bool *extensible) const { // See above. *extensible = true; return true; } bool nsOuterWindowProxy::has(JSContext *cx, JS::Handle proxy, JS::Handle id, bool *bp) const { if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { *bp = true; return true; } return js::Wrapper::has(cx, proxy, id, bp); } bool nsOuterWindowProxy::hasOwn(JSContext *cx, JS::Handle proxy, JS::Handle id, bool *bp) const { if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { *bp = true; return true; } return js::Wrapper::hasOwn(cx, proxy, id, bp); } bool nsOuterWindowProxy::get(JSContext *cx, JS::Handle proxy, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) const { if (id == nsDOMClassInfo::sWrappedJSObject_id && xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) { vp.set(JS::ObjectValue(*proxy)); return true; } bool found; if (!GetSubframeWindow(cx, proxy, id, vp, found)) { return false; } if (found) { return true; } // Else fall through to js::Wrapper return js::Wrapper::get(cx, proxy, receiver, id, vp); } bool nsOuterWindowProxy::set(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult &result) const { int32_t index = GetArrayIndexFromId(cx, id); if (IsArrayIndex(index)) { // Reject the set. It's up to the caller to decide whether to throw a // TypeError. If the caller is strict mode JS code, it'll throw. return result.failReadOnly(); } return js::Wrapper::set(cx, proxy, id, v, receiver, result); } bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle proxy, JS::AutoIdVector &props) const { // BaseProxyHandler::keys seems to do what we want here: call // ownPropertyKeys and then filter out the non-enumerable properties. return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); } bool nsOuterWindowProxy::enumerate(JSContext *cx, JS::Handle proxy, JS::MutableHandle objp) const { // BaseProxyHandler::enumerate seems to do what we want here: fall // back on the property names returned from js::GetPropertyKeys() return js::BaseProxyHandler::enumerate(cx, proxy, objp); } bool nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle vp, bool& found) const { nsCOMPtr frame = GetSubframeWindow(cx, proxy, id); if (!frame) { found = false; return true; } found = true; // Just return the window's global nsGlobalWindow* global = static_cast(frame.get()); global->EnsureInnerWindow(); JSObject* obj = global->FastGetGlobalJSObject(); // This null check fixes a hard-to-reproduce crash that occurs when we // get here when we're mid-call to nsDocShell::Destroy. See bug 640904 // comment 105. if (MOZ_UNLIKELY(!obj)) { return xpc::Throw(cx, NS_ERROR_FAILURE); } vp.setObject(*obj); return JS_WrapValue(cx, vp); } already_AddRefed nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, JS::Handle proxy, JS::Handle id) const { int32_t index = GetArrayIndexFromId(cx, id); if (!IsArrayIndex(index)) { return nullptr; } nsGlobalWindow* win = GetOuterWindow(proxy); MOZ_ASSERT(win->IsOuterWindow()); return win->IndexedGetterOuter(index); } bool nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, JS::AutoIdVector &props) const { uint32_t length = GetOuterWindow(proxy)->Length(); MOZ_ASSERT(int32_t(length) >= 0); if (!props.reserve(props.length() + length)) { return false; } for (int32_t i = 0; i < int32_t(length); ++i) { props.append(INT_TO_JSID(i)); } return true; } bool nsOuterWindowProxy::watch(JSContext *cx, JS::Handle proxy, JS::Handle id, JS::Handle callable) const { return js::WatchGuts(cx, proxy, id, callable); } bool nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle proxy, JS::Handle id) const { return js::UnwatchGuts(cx, proxy, id); } void nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old) { nsGlobalWindow* outerWindow = GetOuterWindow(obj); if (outerWindow) { outerWindow->UpdateWrapper(obj, old); } } const nsOuterWindowProxy nsOuterWindowProxy::singleton; class nsChromeOuterWindowProxy : public nsOuterWindowProxy { public: MOZ_CONSTEXPR nsChromeOuterWindowProxy() : nsOuterWindowProxy() { } virtual const char *className(JSContext *cx, JS::Handle wrapper) const override; static const nsChromeOuterWindowProxy singleton; }; const char * nsChromeOuterWindowProxy::className(JSContext *cx, JS::Handle proxy) const { MOZ_ASSERT(js::IsProxy(proxy)); return "ChromeWindow"; } const nsChromeOuterWindowProxy nsChromeOuterWindowProxy::singleton; static JSObject* NewOuterWindowProxy(JSContext *cx, JS::Handle global, bool isChrome) { JSAutoCompartment ac(cx, global); MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(global) == global); js::WrapperOptions options; options.setClass(&OuterWindowProxyClass); options.setSingleton(true); JSObject *obj = js::Wrapper::New(cx, global, isChrome ? &nsChromeOuterWindowProxy::singleton : &nsOuterWindowProxy::singleton, options); MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj)); return obj; } //***************************************************************************** //*** nsGlobalWindow: Object Management //***************************************************************************** nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) : nsPIDOMWindow(aOuterWindow), mIdleFuzzFactor(0), mIdleCallbackIndex(-1), mCurrentlyIdle(false), mAddActiveEventFuzzTime(true), mIsFrozen(false), mFullScreen(false), mFullscreenMode(false), mIsClosed(false), mInClose(false), mHavePendingClose(false), mHadOriginalOpener(false), mIsPopupSpam(false), mBlockScriptedClosingFlag(false), mWasOffline(false), mNotifyIdleObserversIdleOnThaw(false), mNotifyIdleObserversActiveOnThaw(false), mCreatingInnerWindow(false), mIsChrome(false), mCleanMessageManager(false), mNeedsFocus(true), mHasFocus(false), #if defined(XP_MACOSX) mShowAccelerators(false), mShowFocusRings(false), #else mShowAccelerators(true), mShowFocusRings(true), #endif mShowFocusRingForContent(false), mFocusByKeyOccurred(false), mInnerObjectsFreed(false), mHasGamepad(false), #ifdef MOZ_GAMEPAD mHasSeenGamepadInput(false), #endif mNotifiedIDDestroyed(false), mAllowScriptsToClose(false), mTimeoutInsertionPoint(nullptr), mTimeoutPublicIdCounter(1), mTimeoutFiringDepth(0), mTimeoutsSuspendDepth(0), mFocusMethod(0), mSerial(0), mIdleRequestCallbackCounter(1), #ifdef DEBUG mSetOpenerWindowCalled(false), #endif #ifdef MOZ_B2G mNetworkUploadObserverEnabled(false), mNetworkDownloadObserverEnabled(false), #endif mCleanedUp(false), mDialogAbuseCount(0), mAreDialogsEnabled(true), mCanSkipCCGeneration(0), mVRDevicesInitialized(false) { AssertIsOnMainThread(); nsLayoutStatics::AddRef(); // Initialize the PRCList (this). PR_INIT_CLIST(this); if (aOuterWindow) { // |this| is an inner window, add this inner window to the outer // window list of inners. PR_INSERT_AFTER(this, aOuterWindow); mObserver = new nsGlobalWindowObserver(this); if (mObserver) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { // Watch for online/offline status changes so we can fire events. Use // a strong reference. os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false); // Watch for dom-storage2-changed so we can fire storage // events. Use a strong reference. os->AddObserver(mObserver, "dom-storage2-changed", false); } Preferences::AddStrongObserver(mObserver, "intl.accept_languages"); } } else { // |this| is an outer window. Outer windows start out frozen and // remain frozen until they get an inner window, so freeze this // outer window here. Freeze(); } // We could have failed the first time through trying // to create the entropy collector, so we should // try to get one until we succeed. gRefCnt++; if (gRefCnt == 1) { Preferences::AddIntVarCache(&gMinTimeoutValue, "dom.min_timeout_value", DEFAULT_MIN_TIMEOUT_VALUE); Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue, "dom.min_background_timeout_value", DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE); Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled, "dom.idle-observers-api.fuzz_time.disabled", false); // Precompute variables for Mach factor/load monitoring and idle callbacks // (TenFourFox issue 463). Preferences::AddIntVarCache(&sMachFactorMin, "tenfourfox.dom.requestIdleCallback.mach_factor_min", MACH_FACTOR_MIN); Preferences::AddIntVarCache(&sIdleCallbackMachCheckInterval, "tenfourfox.dom.requestIdleCallback.mach_check_interval", MACH_CHECK_INTERVAL); Preferences::AddIntVarCache(&sIdleCallbackIdleInterval, "tenfourfox.dom.requestIdleCallback.idle_interval", CALLBACK_IDLE_INTERVAL); sMachHost = mach_host_self(); kern_return_t ret = processor_set_default(sMachHost, &sMachDefaultPset); sNumCPUs = 1; if (ret != KERN_SUCCESS) { fprintf(stderr, "TenFourFox: Unable to initialize Mach idle monitoring: %i\n", (uint32_t)ret); sMachFactorMin = 0; } int mib[2] = { CTL_HW, HW_NCPU }; size_t len = sizeof(sNumCPUs); if (sysctl(mib, 2, &sNumCPUs, &len, NULL, 0) == -1) sNumCPUs = 1; #if DEBUG fprintf(stderr, "GlobalWindow: %i CPUs\n", sNumCPUs); #endif } if (gDumpFile == nullptr) { const nsAdoptingCString& fname = Preferences::GetCString("browser.dom.window.dump.file"); if (!fname.IsEmpty()) { // if this fails to open, Dump() knows to just go to stdout // on null. gDumpFile = fopen(fname, "wb+"); } else { gDumpFile = stdout; } } mSerial = ++gSerialCounter; #ifdef DEBUG if (!PR_GetEnv("MOZ_QUIET")) { printf_stderr("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n", gRefCnt, static_cast(ToCanonicalSupports(this)), getpid(), gSerialCounter, static_cast(ToCanonicalSupports(aOuterWindow))); } #endif if (gDOMLeakPRLog) MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, ("DOMWINDOW %p created outer=%p", this, aOuterWindow)); NS_ASSERTION(sWindowsById, "Windows hash table must be created!"); NS_ASSERTION(!sWindowsById->Get(mWindowID), "This window shouldn't be in the hash table yet!"); // We seem to see crashes in release builds because of null |sWindowsById|. if (sWindowsById) { sWindowsById->Put(mWindowID, this); } } #ifdef DEBUG /* static */ void nsGlobalWindow::AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); } #endif // DEBUG /* static */ void nsGlobalWindow::Init() { AssertIsOnMainThread(); CallGetService(NS_ENTROPYCOLLECTOR_CONTRACTID, &gEntropyCollector); NS_ASSERTION(gEntropyCollector, "gEntropyCollector should have been initialized!"); NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!"); sWindowsById = new WindowByIdTable(); } nsGlobalWindow::~nsGlobalWindow() { AssertIsOnMainThread(); DisconnectEventTargetObjects(); // We have to check if sWindowsById isn't null because ::Shutdown might have // been called. if (sWindowsById) { NS_ASSERTION(sWindowsById->Get(mWindowID), "This window should be in the hash table"); sWindowsById->Remove(mWindowID); } --gRefCnt; #ifdef DEBUG if (!PR_GetEnv("MOZ_QUIET")) { nsAutoCString url; if (mLastOpenedURI) { mLastOpenedURI->GetSpec(url); // Data URLs can be very long, so truncate to avoid flooding the log. const uint32_t maxURLLength = 1000; if (url.Length() > maxURLLength) { url.Truncate(maxURLLength); } } nsGlobalWindow* outer = static_cast(mOuterWindow.get()); printf_stderr("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = %s]\n", gRefCnt, static_cast(ToCanonicalSupports(this)), getpid(), mSerial, static_cast(ToCanonicalSupports(outer)), url.get()); } #endif if (gDOMLeakPRLog) MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, ("DOMWINDOW %p destroyed", this)); if (IsOuterWindow()) { JSObject *proxy = GetWrapperPreserveColor(); if (proxy) { js::SetProxyExtra(proxy, 0, js::PrivateValue(nullptr)); } // An outer window is destroyed with inner windows still possibly // alive, iterate through the inner windows and null out their // back pointer to this outer, and pull them out of the list of // inner windows. nsGlobalWindow *w; while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) { PR_REMOVE_AND_INIT_LINK(w); } DropOuterWindowDocs(); } else { Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, mMutationBits ? 1 : 0); if (mListenerManager) { mListenerManager->Disconnect(); mListenerManager = nullptr; } // An inner window is destroyed, pull it out of the outer window's // list if inner windows. PR_REMOVE_LINK(this); // If our outer window's inner window is this window, null out the // outer window's reference to this window that's being deleted. nsGlobalWindow *outer = GetOuterWindowInternal(); if (outer) { outer->MaybeClearInnerWindow(this); } } // Outer windows are always supposed to call CleanUp before letting themselves // be destroyed. And while CleanUp generally seems to be intended to clean up // outers, we've historically called it for both. Changing this would probably // involve auditing all of the references that inners and outers can have, and // separating the handling into CleanUp() and FreeInnerObjects. if (IsInnerWindow()) { CleanUp(); } else { MOZ_ASSERT(mCleanedUp); } nsCOMPtr ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); if (ac) ac->RemoveWindowAsListener(this); nsLayoutStatics::Release(); } void nsGlobalWindow::AddEventTargetObject(DOMEventTargetHelper* aObject) { MOZ_ASSERT(IsInnerWindow()); mEventTargetObjects.PutEntry(aObject); } void nsGlobalWindow::RemoveEventTargetObject(DOMEventTargetHelper* aObject) { MOZ_ASSERT(IsInnerWindow()); mEventTargetObjects.RemoveEntry(aObject); } void nsGlobalWindow::DisconnectEventTargetObjects() { for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); iter.Next()) { RefPtr target = iter.Get()->GetKey(); target->DisconnectFromOwner(); } mEventTargetObjects.Clear(); } // static void nsGlobalWindow::ShutDown() { AssertIsOnMainThread(); if (gDumpFile && gDumpFile != stdout) { fclose(gDumpFile); } gDumpFile = nullptr; NS_IF_RELEASE(gEntropyCollector); delete sWindowsById; sWindowsById = nullptr; } // static void nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow) { if (aWindow->mCachedXBLPrototypeHandlers && aWindow->mCachedXBLPrototypeHandlers->Count() > 0) { aWindow->mCachedXBLPrototypeHandlers->Clear(); } } void nsGlobalWindow::MaybeForgiveSpamCount() { if (IsOuterWindow() && IsPopupSpamWindow()) { SetIsPopupSpamWindow(false); } } void nsGlobalWindow::SetIsPopupSpamWindow(bool aIsPopupSpam) { MOZ_ASSERT(IsOuterWindow()); mIsPopupSpam = aIsPopupSpam; if (aIsPopupSpam) { ++gOpenPopupSpamCount; } else { --gOpenPopupSpamCount; NS_ASSERTION(gOpenPopupSpamCount >= 0, "Unbalanced decrement of gOpenPopupSpamCount"); } } void nsGlobalWindow::DropOuterWindowDocs() { MOZ_ASSERT(IsOuterWindow()); MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed()); mDoc = nullptr; mSuspendedDoc = nullptr; } void nsGlobalWindow::CleanUp() { // Guarantee idempotence. if (mCleanedUp) return; mCleanedUp = true; StartDying(); DisconnectEventTargetObjects(); if (mObserver) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); os->RemoveObserver(mObserver, "dom-storage2-changed"); } #ifdef MOZ_B2G DisableNetworkEvent(eNetworkUpload); DisableNetworkEvent(eNetworkDownload); #endif // MOZ_B2G if (mIdleService) { mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); } Preferences::RemoveObserver(mObserver, "intl.accept_languages"); // Drop its reference to this dying window, in case for some bogus reason // the object stays around. mObserver->Forget(); } if (mNavigator) { mNavigator->Invalidate(); mNavigator = nullptr; } mScreen = nullptr; mMenubar = nullptr; mToolbar = nullptr; mLocationbar = nullptr; mPersonalbar = nullptr; mStatusbar = nullptr; mScrollbars = nullptr; mLocation = nullptr; mHistory = nullptr; mFrames = nullptr; mWindowUtils = nullptr; mApplicationCache = nullptr; mIndexedDB = nullptr; mConsole = nullptr; mExternal = nullptr; mMozSelfSupport = nullptr; mPerformance = nullptr; #ifdef MOZ_WEBSPEECH mSpeechSynthesis = nullptr; #endif #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) mOrientationChangeObserver = nullptr; #endif ClearControllers(); mOpener = nullptr; // Forces Release if (mContext) { mContext = nullptr; // Forces Release } mChromeEventHandler = nullptr; // Forces Release mParentTarget = nullptr; if (IsOuterWindow()) { nsGlobalWindow* inner = GetCurrentInnerWindowInternal(); if (inner) { inner->CleanUp(); } } if (IsInnerWindow()) { DisableGamepadUpdates(); mHasGamepad = false; } else { MOZ_ASSERT(!mHasGamepad); } if (mCleanMessageManager) { MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned"); nsGlobalChromeWindow *asChrome = static_cast(this); if (asChrome->mMessageManager) { static_cast( asChrome->mMessageManager.get())->Disconnect(); } } mArguments = nullptr; mDialogArguments = nullptr; CleanupCachedXBLHandlers(this); for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { mAudioContexts[i]->Shutdown(); } mAudioContexts.Clear(); if (mIdleTimer) { mIdleTimer->Cancel(); mIdleTimer = nullptr; } DisableTimeChangeNotifications(); mServiceWorkerRegistrationTable.Clear(); } void nsGlobalWindow::ClearControllers() { if (mControllers) { uint32_t count; mControllers->GetControllerCount(&count); while (count--) { nsCOMPtr controller; mControllers->GetControllerAt(count, getter_AddRefs(controller)); nsCOMPtr context = do_QueryInterface(controller); if (context) context->SetCommandContext(nullptr); } mControllers = nullptr; } } void nsGlobalWindow::FreeInnerObjects(bool aForDocumentOpen) { NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window"); // Make sure that this is called before we null out the document and // other members that the window destroyed observers could // re-create. NotifyDOMWindowDestroyed(this); mInnerObjectsFreed = true; // Kill all of the workers for this window. mozilla::dom::workers::CancelWorkersForWindow(this); ClearAllTimeouts(); if (mIdleTimer) { mIdleTimer->Cancel(); mIdleTimer = nullptr; } mIdleObservers.Clear(); mChromeEventHandler = nullptr; if (mListenerManager) { mListenerManager->Disconnect(); mListenerManager = nullptr; } mLocation = nullptr; mHistory = nullptr; if (mNavigator) { mNavigator->OnNavigation(); mNavigator->Invalidate(); mNavigator = nullptr; } if (mScreen) { mScreen = nullptr; } #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) mOrientationChangeObserver = nullptr; #endif if (mDoc) { // Remember the document's principal and URI. mDocumentPrincipal = mDoc->NodePrincipal(); mDocumentURI = mDoc->GetDocumentURI(); mDocBaseURI = mDoc->GetDocBaseURI(); if (!aForDocumentOpen) { while (mDoc->EventHandlingSuppressed()) { mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false); } } // Note: we don't have to worry about eAnimationsOnly suppressions because // they won't leak. } // Remove our reference to the document and the document principal. mFocusedNode = nullptr; if (mApplicationCache) { static_cast(mApplicationCache.get())->Disconnect(); mApplicationCache = nullptr; } mIndexedDB = nullptr; NotifyWindowIDDestroyed("inner-window-destroyed"); CleanupCachedXBLHandlers(this); for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { mAudioContexts[i]->Shutdown(); } mAudioContexts.Clear(); #ifdef MOZ_GAMEPAD DisableGamepadUpdates(); mHasGamepad = false; mGamepads.Clear(); #endif } //***************************************************************************** // nsGlobalWindow::nsISupports //***************************************************************************** // QueryInterface implementation for nsGlobalWindow NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindow) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY // Make sure this matches the cast in nsGlobalWindow::FromWrapper() NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventTarget) NS_INTERFACE_MAP_ENTRY(nsIDOMWindow) if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) { foundInterface = static_cast(this); if (!sWarnedAboutWindowInternal) { sWarnedAboutWindowInternal = true; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Extensions"), mDoc, nsContentUtils::eDOM_PROPERTIES, "nsIDOMWindowInternalWarning"); } } else NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject) NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget) NS_INTERFACE_MAP_ENTRY(nsPIDOMWindow) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindow) static PLDHashOperator MarkXBLHandlers(nsXBLPrototypeHandler* aKey, JS::Heap& aData, void* aClosure) { JS::ExposeObjectToActiveJS(aData); return PL_DHASH_NEXT; } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindow) if (tmp->IsBlackForCC(false)) { if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) { return true; } tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration; if (tmp->mCachedXBLPrototypeHandlers) { tmp->mCachedXBLPrototypeHandlers->Enumerate(MarkXBLHandlers, nullptr); } if (EventListenerManager* elm = tmp->GetExistingListenerManager()) { elm->MarkForCC(); } tmp->UnmarkGrayTimers(); return true; } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindow) return tmp->IsBlackForCC(true); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow) return tmp->IsBlackForCC(false); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END inline void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, IdleObserverHolder& aField, const char* aName, unsigned aFlags) { CycleCollectionNoteChild(aCallback, aField.mIdleObserver.get(), aName, aFlags); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) if (MOZ_UNLIKELY(cb.WantDebugInfo())) { char name[512]; nsAutoCString uri; if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) { tmp->mDoc->GetDocumentURI()->GetSpec(uri); } PR_snprintf(name, sizeof(name), "nsGlobalWindow #%llu %s %s", tmp->mWindowID, tmp->IsInnerWindow() ? "inner" : "outer", uri.get()); cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); } else { NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindow, tmp->mRefCnt.get()) } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDialogArguments) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerRegistrationTable) #ifdef MOZ_WEBSPEECH NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis) #endif NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) for (nsTimeout* timeout = tmp->mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) { cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(nsTimeout)); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) #ifdef MOZ_GAMEPAD NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads) #endif NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDevices) // Traverse stuff from nsPIDOMWindow NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedNode) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocationbar) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersonalbar) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMozSelfSupport) tmp->TraverseHostObjectURIs(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) nsGlobalWindow::CleanupCachedXBLHandlers(tmp); NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorkerRegistrationTable) #ifdef MOZ_WEBSPEECH NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis) #endif if (tmp->mOuterWindow) { static_cast(tmp->mOuterWindow.get())->MaybeClearInnerWindow(tmp); NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow) } if (tmp->mListenerManager) { tmp->mListenerManager->Disconnect(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager) } NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation) NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage) if (tmp->mApplicationCache) { static_cast(tmp->mApplicationCache.get())->Disconnect(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache) } NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers) #ifdef MOZ_GAMEPAD NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads) #endif NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage) NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDevices) // Unlink stuff from nsPIDOMWindow NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedNode) NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar) NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar) NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar) NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto) NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole) NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport) tmp->UnlinkHostObjectURIs(); NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END #ifdef DEBUG void nsGlobalWindow::RiskyUnlink() { NS_CYCLE_COLLECTION_INNERNAME.Unlink(this); } #endif struct TraceData { const TraceCallbacks& callbacks; void* closure; }; static PLDHashOperator TraceXBLHandlers(nsXBLPrototypeHandler* aKey, JS::Heap& aData, void* aClosure) { TraceData* data = static_cast(aClosure); data->callbacks.Trace(&aData, "Cached XBL prototype handler", data->closure); return PL_DHASH_NEXT; } NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindow) if (tmp->mCachedXBLPrototypeHandlers) { TraceData data = { aCallbacks, aClosure }; tmp->mCachedXBLPrototypeHandlers->Enumerate(TraceXBLHandlers, &data); } NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END bool nsGlobalWindow::IsBlackForCC(bool aTracingNeeded) { if (!nsCCUncollectableMarker::sGeneration) { return false; } return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) || IsBlack()) && (!aTracingNeeded || HasNothingToTrace(static_cast(this))); } void nsGlobalWindow::UnmarkGrayTimers() { for (nsTimeout* timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) { if (timeout->mScriptHandler) { Function* f = timeout->mScriptHandler->GetCallback(); if (f) { f->MarkForCC(); } } } } //***************************************************************************** // nsGlobalWindow::nsIScriptGlobalObject //***************************************************************************** nsresult nsGlobalWindow::EnsureScriptEnvironment() { nsGlobalWindow* outer = GetOuterWindowInternal(); if (!outer) { NS_WARNING("No outer window available!"); return NS_ERROR_FAILURE; } if (outer->GetWrapperPreserveColor()) { return NS_OK; } NS_ASSERTION(!outer->GetCurrentInnerWindowInternal(), "No cached wrapper, but we have an inner window?"); // If this window is a [i]frame, don't bother GC'ing when the frame's context // is destroyed since a GC will happen when the frameset or host document is // destroyed anyway. nsCOMPtr context = new nsJSContext(!IsFrame(), outer); NS_ASSERTION(!outer->mContext, "Will overwrite mContext!"); // should probably assert the context is clean??? context->WillInitializeContext(); nsresult rv = context->InitContext(); NS_ENSURE_SUCCESS(rv, rv); outer->mContext = context; return NS_OK; } nsIScriptContext * nsGlobalWindow::GetScriptContext() { nsGlobalWindow* outer = GetOuterWindowInternal(); return outer ? outer->mContext : nullptr; } JSObject * nsGlobalWindow::GetGlobalJSObject() { return FastGetGlobalJSObject(); } void nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc) { TraceWrapper(aTrc, "active window global"); } bool nsGlobalWindow::WouldReuseInnerWindow(nsIDocument* aNewDocument) { MOZ_ASSERT(IsOuterWindow()); // We reuse the inner window when: // a. We are currently at our original document. // b. At least one of the following conditions are true: // -- The new document is the same as the old document. This means that we're // getting called from document.open(). // -- The new document has the same origin as what we have loaded right now. if (!mDoc || !aNewDocument) { return false; } if (!mDoc->IsInitialDocument()) { return false; } NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()), "How'd this happen?"); // Great, we're the original document, check for one of the other // conditions. if (mDoc == aNewDocument) { return true; } bool equal; if (NS_SUCCEEDED( BasePrincipal::Cast(mDoc->NodePrincipal())-> EqualsConsideringDomain(aNewDocument->NodePrincipal(), &equal))) { // Return the result. If true (bug 1552541): // The origin is the same. return equal; } return false; } void nsGlobalWindow::SetInitialPrincipalToSubject() { MOZ_ASSERT(IsOuterWindow()); // First, grab the subject principal. nsCOMPtr newWindowPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); // Now, if we're about to use the system principal or an nsExpandedPrincipal, // make sure we're not using it for a content docshell. if (nsContentUtils::IsSystemOrExpandedPrincipal(newWindowPrincipal) && GetDocShell()->ItemType() != nsIDocShellTreeItem::typeChrome) { newWindowPrincipal = nullptr; } // If there's an existing document, bail if it either: if (mDoc) { // (a) is not an initial about:blank document, or if (!mDoc->IsInitialDocument()) return; // (b) already has the correct principal. if (mDoc->NodePrincipal() == newWindowPrincipal) return; #ifdef DEBUG // If we have a document loaded at this point, it had better be about:blank. // Otherwise, something is really weird. nsCOMPtr uri; mDoc->NodePrincipal()->GetURI(getter_AddRefs(uri)); NS_ASSERTION(uri && NS_IsAboutBlank(uri) && NS_IsAboutBlank(mDoc->GetDocumentURI()), "Unexpected original document"); #endif } GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal); mDoc->SetIsInitialDocument(true); nsCOMPtr shell = GetDocShell()->GetPresShell(); if (shell && !shell->DidInitialize()) { // Ensure that if someone plays with this document they will get // layout happening. nsRect r = shell->GetPresContext()->GetVisibleArea(); shell->Initialize(r.width, r.height); } } PopupControlState PushPopupControlState(PopupControlState aState, bool aForce) { MOZ_ASSERT(NS_IsMainThread()); PopupControlState oldState = gPopupControlState; if (aState < gPopupControlState || aForce) { gPopupControlState = aState; } return oldState; } void PopPopupControlState(PopupControlState aState) { MOZ_ASSERT(NS_IsMainThread()); gPopupControlState = aState; } PopupControlState nsGlobalWindow::PushPopupControlState(PopupControlState aState, bool aForce) const { return ::PushPopupControlState(aState, aForce); } void nsGlobalWindow::PopPopupControlState(PopupControlState aState) const { ::PopPopupControlState(aState); } PopupControlState nsGlobalWindow::GetPopupControlState() const { MOZ_ASSERT(NS_IsMainThread()); return gPopupControlState; } #define WINDOWSTATEHOLDER_IID \ {0x0b917c3e, 0xbd50, 0x4683, {0xaf, 0xc9, 0xc7, 0x81, 0x07, 0xae, 0x33, 0x26}} class WindowStateHolder final : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(WINDOWSTATEHOLDER_IID) NS_DECL_ISUPPORTS WindowStateHolder(nsIScriptContext* aContext, nsGlobalWindow *aWindow); nsGlobalWindow* GetInnerWindow() { return mInnerWindow; } void DidRestoreWindow() { mInnerWindow = nullptr; mInnerWindowReflector = nullptr; } protected: ~WindowStateHolder(); nsGlobalWindow *mInnerWindow; // We hold onto this to make sure the inner window doesn't go away. The outer // window ends up recalculating it anyway. JS::PersistentRooted mInnerWindowReflector; }; NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID) WindowStateHolder::WindowStateHolder(nsIScriptContext* aContext, nsGlobalWindow* aWindow) : mInnerWindow(aWindow), mInnerWindowReflector(aContext->GetNativeContext(), aWindow->GetWrapper()) { NS_PRECONDITION(aWindow, "null window"); NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window"); aWindow->SuspendTimeouts(); // When a global goes into the bfcache, we disable script. xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(false); } WindowStateHolder::~WindowStateHolder() { if (mInnerWindow) { // This window was left in the bfcache and is now going away. We need to // free it up. // Note that FreeInnerObjects may already have been called on the // inner window if its outer has already had SetDocShell(null) // called. mInnerWindow->FreeInnerObjects(); } } NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder) // We need certain special behavior for remote XUL whitelisted domains, but we // don't want that behavior to take effect in automation, because we whitelist // all the mochitest domains. So we need to check a pref here. static bool TreatAsRemoteXUL(nsIPrincipal* aPrincipal) { MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(aPrincipal)); return nsContentUtils::AllowXULXBLForPrincipal(aPrincipal) && !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); } static bool EnablePrivilege(JSContext* cx, unsigned argc, JS::Value* vp) { Telemetry::Accumulate(Telemetry::ENABLE_PRIVILEGE_EVER_CALLED, true); return xpc::EnableUniversalXPConnect(cx); } static const JSFunctionSpec EnablePrivilegeSpec[] = { JS_FS("enablePrivilege", EnablePrivilege, 1, 0), JS_FS_END }; static bool InitializeLegacyNetscapeObject(JSContext* aCx, JS::Handle aGlobal) { JSAutoCompartment ac(aCx, aGlobal); // Note: MathJax depends on window.netscape being exposed. See bug 791526. JS::Rooted obj(aCx); obj = JS_DefineObject(aCx, aGlobal, "netscape", nullptr); NS_ENSURE_TRUE(obj, false); obj = JS_DefineObject(aCx, obj, "security", nullptr); NS_ENSURE_TRUE(obj, false); // We hide enablePrivilege behind a pref because it has been altered in a // way that makes it fundamentally insecure to use in production. Mozilla // uses this pref during automated testing to support legacy test code that // uses enablePrivilege. If you're not doing test automation, you _must_ not // flip this pref, or you will be exposing all your users to security // vulnerabilities. if (!Preferences::GetBool("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer")) { return true; } /* Define PrivilegeManager object with the necessary "static" methods. */ obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr); NS_ENSURE_TRUE(obj, false); return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec); } /** * Create a new global object that will be used for an inner window. * Return the native global and an nsISupports 'holder' that can be used * to manage the lifetime of it. */ static nsresult CreateNativeGlobalForInner(JSContext* aCx, nsGlobalWindow* aNewInner, nsIURI* aURI, nsIPrincipal* aPrincipal, JS::MutableHandle aGlobal) { MOZ_ASSERT(aCx); MOZ_ASSERT(aNewInner); MOZ_ASSERT(aNewInner->IsInnerWindow()); MOZ_ASSERT(aPrincipal); // DOMWindow with nsEP is not supported, we have to make sure // no one creates one accidentally. nsCOMPtr nsEP = do_QueryInterface(aPrincipal); MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported"); nsGlobalWindow *top = nullptr; if (aNewInner->GetOuterWindow()) { top = aNewInner->GetTopInternal(); } JS::CompartmentOptions options; // Sometimes add-ons load their own XUL windows, either as separate top-level // windows or inside a browser element. In such cases we want to tag the // window's compartment with the add-on ID. See bug 1092156. if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { options.setAddonId(MapURIToAddonID(aURI)); } if (top) { if (top->GetGlobalJSObject()) { options.setSameZoneAs(top->GetGlobalJSObject()); } } // Determine if we need the Components object. bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) || TreatAsRemoteXUL(aPrincipal); uint32_t flags = needComponents ? 0 : nsIXPConnect::OMIT_COMPONENTS_OBJECT; flags |= nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK; if (!WindowBinding::Wrap(aCx, aNewInner, aNewInner, options, nsJSPrincipals::get(aPrincipal), false, aGlobal) || !xpc::InitGlobalObject(aCx, aGlobal, flags)) { return NS_ERROR_FAILURE; } MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal); // Set the location information for the new global, so that tools like // about:memory may use that information xpc::SetLocationForGlobal(aGlobal, aURI); if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, nsISupports* aState, bool aForceReuseInnerWindow) { NS_PRECONDITION(mDocumentPrincipal == nullptr, "mDocumentPrincipal prematurely set!"); MOZ_ASSERT(aDocument); if (IsInnerWindow()) { if (!mOuterWindow) { return NS_ERROR_NOT_INITIALIZED; } // Refuse to set a new document if the call came from an inner // window that's not the current inner window. if (mOuterWindow->GetCurrentInnerWindow() != this) { return NS_ERROR_NOT_AVAILABLE; } return GetOuterWindowInternal()->SetNewDocument(aDocument, aState, aForceReuseInnerWindow); } NS_PRECONDITION(IsOuterWindow(), "Must only be called on outer windows"); // Bail out early if we're in process of closing down the window. NS_ENSURE_STATE(!mCleanedUp); if (IsFrozen()) { // This outer is now getting its first inner, thaw the outer now // that it's ready and is getting an inner window. Thaw(); } NS_ASSERTION(!GetCurrentInnerWindow() || GetCurrentInnerWindow()->GetExtantDoc() == mDoc, "Uh, mDoc doesn't match the current inner window " "document!"); bool wouldReuseInnerWindow = WouldReuseInnerWindow(aDocument); if (aForceReuseInnerWindow && !wouldReuseInnerWindow && mDoc && mDoc->NodePrincipal() != aDocument->NodePrincipal()) { NS_ERROR("Attempted forced inner window reuse while changing principal"); return NS_ERROR_UNEXPECTED; } nsCOMPtr oldDoc = mDoc; AutoJSAPI jsapi; jsapi.Init(); JSContext *cx = jsapi.cx(); // Check if we're anywhere near the stack limit before we reach the // transplanting code, since it has no good way to handle errors. This uses // the untrusted script limit, which is not strictly necessary since no // actual script should run. if (MOZ_UNLIKELY(!js::CheckRecursionConservativeDontReport(cx))) { NS_WARNING("Overrecursion in SetNewDocument"); return NS_ERROR_FAILURE; } if (!mDoc) { // First document load. // Get our private root. If it is equal to us, then we need to // attach our global key bindings that handles browser scrolling // and other browser commands. nsIDOMWindow* privateRoot = nsGlobalWindow::GetPrivateRoot(); if (privateRoot == static_cast(this)) { nsXBLService::AttachGlobalKeyHandler(mChromeEventHandler); } } /* No mDocShell means we're already been partially closed down. When that happens, setting status isn't a big requirement, so don't. (Doesn't happen under normal circumstances, but bug 49615 describes a case.) */ nsContentUtils::AddScriptRunner( NS_NewRunnableMethod(this, &nsGlobalWindow::ClearStatus)); // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner // window (see bug 776497). Be safe. bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) && GetCurrentInnerWindowInternal(); nsresult rv = NS_OK; // We set mDoc even though this is an outer window to avoid // having to *always* reach into the inner window to find the // document. mDoc = aDocument; // Take this opportunity to clear mSuspendedDoc. Our old inner window is now // responsible for unsuspending it. mSuspendedDoc = nullptr; #ifdef DEBUG mLastOpenedURI = aDocument->GetDocumentURI(); #endif mContext->WillInitializeContext(); nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); if (currentInner && currentInner->mNavigator) { currentInner->mNavigator->OnNavigation(); } RefPtr newInnerWindow; bool createdInnerWindow = false; bool thisChrome = IsChromeWindow(); nsCOMPtr wsh = do_QueryInterface(aState); NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?"); bool handleDocumentOpen = false; JS::Rooted newInnerGlobal(cx); if (reUseInnerWindow) { // We're reusing the current inner window. NS_ASSERTION(!currentInner->IsFrozen(), "We should never be reusing a shared inner window"); newInnerWindow = currentInner; newInnerGlobal = currentInner->GetWrapperPreserveColor(); if (aDocument != oldDoc) { JS::ExposeObjectToActiveJS(newInnerGlobal); } // We're reusing the inner window, but this still counts as a navigation, // so all expandos and such defined on the outer window should go away. Force // all Xray wrappers to be recomputed. JS::Rooted rootedObject(cx, GetWrapperPreserveColor()); JS::ExposeObjectToActiveJS(rootedObject); if (!JS_RefreshCrossCompartmentWrappers(cx, rootedObject)) { return NS_ERROR_FAILURE; } // Inner windows are only reused for same-origin principals, but the principals // don't necessarily match exactly. Update the principal on the compartment to // match the new document. // NB: We don't just call currentInner->RefreshCompartmentPrincipals() here // because we haven't yet set its mDoc to aDocument. JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal); #ifdef DEBUG bool sameOrigin = false; nsIPrincipal *existing = nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment)); aDocument->NodePrincipal()->Equals(existing, &sameOrigin); MOZ_ASSERT(sameOrigin); #endif JS_SetCompartmentPrincipals(compartment, nsJSPrincipals::get(aDocument->NodePrincipal())); } else { if (aState) { newInnerWindow = wsh->GetInnerWindow(); newInnerGlobal = newInnerWindow->GetWrapperPreserveColor(); } else { if (thisChrome) { newInnerWindow = nsGlobalChromeWindow::Create(this); } else if (mIsModalContentWindow) { newInnerWindow = nsGlobalModalWindow::Create(this); } else { newInnerWindow = nsGlobalWindow::Create(this); } // Freeze the outer window and null out the inner window so // that initializing classes on the new inner doesn't end up // reaching into the old inner window for classes etc. // // [This happens with Object.prototype when XPConnect creates // a temporary global while initializing classes; the reason // being that xpconnect creates the temp global w/o a parent // and proto, which makes the JS engine look up classes in // cx->globalObject, i.e. this outer window]. mInnerWindow = nullptr; Freeze(); mCreatingInnerWindow = true; // Every script context we are initialized with must create a // new global. rv = CreateNativeGlobalForInner(cx, newInnerWindow, aDocument->GetDocumentURI(), aDocument->NodePrincipal(), &newInnerGlobal); NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal && newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal, "Failed to get script global"); mCreatingInnerWindow = false; createdInnerWindow = true; Thaw(); NS_ENSURE_SUCCESS(rv, rv); } if (currentInner && currentInner->GetWrapperPreserveColor()) { if (oldDoc == aDocument) { handleDocumentOpen = true; // Move the navigator from the old inner window to the new one since // this is a document.write. This is safe from a same-origin point of // view because document.write can only be used by the same origin. newInnerWindow->mNavigator = currentInner->mNavigator; currentInner->mNavigator = nullptr; if (newInnerWindow->mNavigator) { newInnerWindow->mNavigator->SetWindow(newInnerWindow); } // Make a copy of the old window's performance object on document.open. // Note that we have to force eager creation of it here, because we need // to grab the current document channel and whatnot before that changes. currentInner->CreatePerformanceObjectIfNeeded(); if (currentInner->mPerformance) { newInnerWindow->mPerformance = new nsPerformance(newInnerWindow, currentInner->mPerformance->GetDOMTiming(), currentInner->mPerformance->GetChannel(), currentInner->mPerformance->GetParentPerformance()); } } // Don't free objects on our current inner window if it's going to be // held in the bfcache. if (!currentInner->IsFrozen()) { currentInner->FreeInnerObjects(handleDocumentOpen); } } mInnerWindow = newInnerWindow; if (!GetWrapperPreserveColor()) { JS::Rooted outer(cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE); js::SetProxyExtra(outer, 0, js::PrivateValue(ToSupports(this))); // Inform the nsJSContext, which is the canonical holder of the outer. mContext->SetWindowProxy(outer); mContext->DidInitializeContext(); SetWrapper(mContext->GetWindowProxy()); } else { JS::ExposeObjectToActiveJS(newInnerGlobal); JS::Rooted outerObject(cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); if (!outerObject) { NS_ERROR("out of memory"); return NS_ERROR_FAILURE; } JS::Rooted obj(cx, GetWrapperPreserveColor()); js::SetProxyExtra(obj, 0, js::PrivateValue(nullptr)); js::SetProxyExtra(outerObject, 0, js::PrivateValue(nullptr)); outerObject = xpc::TransplantObject(cx, obj, outerObject); if (!outerObject) { NS_ERROR("unable to transplant wrappers, probably OOM"); return NS_ERROR_FAILURE; } js::SetProxyExtra(outerObject, 0, js::PrivateValue(ToSupports(this))); SetWrapper(outerObject); MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(outerObject) == newInnerGlobal); // Inform the nsJSContext, which is the canonical holder of the outer. mContext->SetWindowProxy(outerObject); } // Enter the new global's compartment. JSAutoCompartment ac(cx, GetWrapperPreserveColor()); { JS::Rooted outer(cx, GetWrapperPreserveColor()); js::SetWindowProxy(cx, newInnerGlobal, outer); } // Set scriptability based on the state of the docshell. bool allow = GetDocShell()->GetCanExecuteScripts(); xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(allow); if (!aState) { // Get the "window" property once so it will be cached on our inner. We // have to do this here, not in binding code, because this has to happen // after we've created the outer window proxy and stashed it in the outer // nsGlobalWindow, so GetWrapperPreserveColor() on that outer // nsGlobalWindow doesn't return null and nsGlobalWindow::OuterObject // works correctly. JS::Rooted unused(cx); if (!JS_GetProperty(cx, newInnerGlobal, "window", &unused)) { NS_ERROR("can't create the 'window' property"); return NS_ERROR_FAILURE; } // And same thing for the "self" property. if (!JS_GetProperty(cx, newInnerGlobal, "self", &unused)) { NS_ERROR("can't create the 'self' property"); return NS_ERROR_FAILURE; } } } JSAutoCompartment ac(cx, GetWrapperPreserveColor()); if (!aState && !reUseInnerWindow) { // Loading a new page and creating a new inner window, *not* // restoring from session history. // Now that both the the inner and outer windows are initialized // let the script context do its magic to hook them together. MOZ_ASSERT(mContext->GetWindowProxy() == GetWrapperPreserveColor()); #ifdef DEBUG JS::Rooted rootedJSObject(cx, GetWrapperPreserveColor()); JS::Rooted proto1(cx), proto2(cx); JS_GetPrototype(cx, rootedJSObject, &proto1); JS_GetPrototype(cx, newInnerGlobal, &proto2); NS_ASSERTION(proto1 == proto2, "outer and inner globals should have the same prototype"); #endif nsCOMPtr frame = GetFrameElementInternal(); if (frame) { nsPIDOMWindow* parentWindow = frame->OwnerDoc()->GetWindow(); if (parentWindow && parentWindow->TimeoutSuspendCount()) { SuspendTimeouts(parentWindow->TimeoutSuspendCount()); } } } // Add an extra ref in case we release mContext during GC. nsCOMPtr kungFuDeathGrip(mContext); aDocument->SetScriptGlobalObject(newInnerWindow); if (!aState) { if (reUseInnerWindow) { if (newInnerWindow->mDoc != aDocument) { newInnerWindow->mDoc = aDocument; // The storage objects contain the URL of the window. We have to // recreate them when the innerWindow is reused. newInnerWindow->mLocalStorage = nullptr; newInnerWindow->mSessionStorage = nullptr; newInnerWindow->ClearDocumentDependentSlots(cx); } } else { newInnerWindow->InnerSetNewDocument(cx, aDocument); // Initialize DOM classes etc on the inner window. JS::Rooted obj(cx, newInnerGlobal); rv = kungFuDeathGrip->InitClasses(obj); NS_ENSURE_SUCCESS(rv, rv); } // If the document comes from a JAR, check if the channel was determined // to be unsafe. If so, permanently disable script on the compartment by // calling Block() and throwing away the key. nsCOMPtr jarChannel = do_QueryInterface(aDocument->GetChannel()); if (jarChannel && jarChannel->GetIsUnsafe()) { xpc::Scriptability::Get(newInnerGlobal).Block(); } if (mArguments) { newInnerWindow->DefineArgumentsProperty(mArguments); mArguments = nullptr; } // Give the new inner window our chrome event handler (since it // doesn't have one). newInnerWindow->mChromeEventHandler = mChromeEventHandler; } nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT); kungFuDeathGrip->DidInitializeContext(); // We wait to fire the debugger hook until the window is all set up and hooked // up with the outer. See bug 969156. if (createdInnerWindow) { nsContentUtils::AddScriptRunner( NS_NewRunnableMethod(newInnerWindow, &nsGlobalWindow::FireOnNewGlobalObject)); } if (newInnerWindow && !newInnerWindow->mHasNotifiedGlobalCreated && mDoc) { // We should probably notify. However if this is the, arguably bad, // situation when we're creating a temporary non-chrome-about-blank // document in a chrome docshell, don't notify just yet. Instead wait // until we have a real chrome doc. if (!mDocShell || mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome || nsContentUtils::IsSystemPrincipal(mDoc->NodePrincipal())) { newInnerWindow->mHasNotifiedGlobalCreated = true; nsContentUtils::AddScriptRunner( NS_NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated)); } } PreloadLocalStorage(); return NS_OK; } void nsGlobalWindow::PreloadLocalStorage() { MOZ_ASSERT(IsOuterWindow()); if (!Preferences::GetBool(kStorageEnabled)) { return; } if (IsChromeWindow()) { return; } nsIPrincipal* principal = GetPrincipal(); if (!principal) { return; } nsresult rv; nsCOMPtr storageManager = do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv); if (NS_FAILED(rv)) { return; } storageManager->PrecacheStorage(principal); } void nsGlobalWindow::DispatchDOMWindowCreated() { MOZ_ASSERT(IsOuterWindow()); if (!mDoc) { return; } // Fire DOMWindowCreated at chrome event listeners nsContentUtils::DispatchChromeEvent(mDoc, mDoc, NS_LITERAL_STRING("DOMWindowCreated"), true /* bubbles */, false /* not cancellable */); nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { nsAutoString origin; nsIPrincipal* principal = mDoc->NodePrincipal(); nsContentUtils::GetUTFOrigin(principal, origin); observerService-> NotifyObservers(static_cast(this), nsContentUtils::IsSystemPrincipal(principal) ? "chrome-document-global-created" : "content-document-global-created", origin.get()); } } void nsGlobalWindow::ClearStatus() { SetStatusOuter(EmptyString()); } void nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument) { NS_PRECONDITION(IsInnerWindow(), "Must only be called on inner windows"); MOZ_ASSERT(aDocument); if (gDOMLeakPRLog && MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { nsIURI *uri = aDocument->GetDocumentURI(); nsAutoCString spec; if (uri) uri->GetSpec(spec); PR_LogPrint("DOMWINDOW %p SetNewDocument %s", this, spec.get()); } mDoc = aDocument; ClearDocumentDependentSlots(aCx); mFocusedNode = nullptr; mLocalStorage = nullptr; mSessionStorage = nullptr; #ifdef DEBUG mLastOpenedURI = aDocument->GetDocumentURI(); #endif Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, mMutationBits ? 1 : 0); // Clear our mutation bitfield. mMutationBits = 0; } void nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell) { NS_ASSERTION(IsOuterWindow(), "Uh, SetDocShell() called on inner window!"); MOZ_ASSERT(aDocShell); if (aDocShell == mDocShell) { return; } mDocShell = aDocShell; // Weak Reference NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!"); if (mFrames) { mFrames->SetDocShell(aDocShell); } // Get our enclosing chrome shell and retrieve its global window impl, so // that we can do some forwarding to the chrome document. nsCOMPtr chromeEventHandler; mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler)); mChromeEventHandler = do_QueryInterface(chromeEventHandler); if (!mChromeEventHandler) { // We have no chrome event handler. If we have a parent, // get our chrome event handler from the parent. If // we don't have a parent, then we need to make a new // window root object that will function as a chrome event // handler and receive all events that occur anywhere inside // our window. nsCOMPtr parentWindow = GetParent(); if (parentWindow.get() != static_cast(this)) { mChromeEventHandler = parentWindow->GetChromeEventHandler(); } else { mChromeEventHandler = NS_NewWindowRoot(this); } } bool docShellActive; mDocShell->GetIsActive(&docShellActive); mIsBackground = !docShellActive; } void nsGlobalWindow::DetachFromDocShell() { NS_ASSERTION(IsOuterWindow(), "Uh, DetachFromDocShell() called on inner window!"); // DetachFromDocShell means the window is being torn down. Drop our // reference to the script context, allowing it to be deleted // later. Meanwhile, keep our weak reference to the script object // so that it can be retrieved later (until it is finalized by the JS GC). NS_ASSERTION(mTimeouts.isEmpty(), "Uh, outer window holds timeouts!"); // Call FreeInnerObjects on all inner windows, not just the current // one, since some could be held by WindowStateHolder objects that // are GC-owned. for (RefPtr inner = (nsGlobalWindow *)PR_LIST_HEAD(this); inner != this; inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) { NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == this, "bad outer window pointer"); inner->FreeInnerObjects(); } // Make sure that this is called before we null out the document. NotifyDOMWindowDestroyed(this); NotifyWindowIDDestroyed("outer-window-destroyed"); nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); if (currentInner) { NS_ASSERTION(mDoc, "Must have doc!"); // Remember the document's principal and URI. mDocumentPrincipal = mDoc->NodePrincipal(); mDocumentURI = mDoc->GetDocumentURI(); mDocBaseURI = mDoc->GetDocBaseURI(); // Release our document reference DropOuterWindowDocs(); mFocusedNode = nullptr; } ClearControllers(); mChromeEventHandler = nullptr; // force release now if (mContext) { nsJSContext::PokeGC(JS::gcreason::SET_DOC_SHELL); mContext = nullptr; } mDocShell = nullptr; // Weak Reference NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!"); if (mFrames) { mFrames->SetDocShell(nullptr); } MaybeForgiveSpamCount(); CleanUp(); } void nsGlobalWindow::SetOpenerWindow(nsIDOMWindow* aOpener, bool aOriginalOpener) { FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener)); NS_ASSERTION(!aOriginalOpener || !mSetOpenerWindowCalled, "aOriginalOpener is true, but not first call to " "SetOpenerWindow!"); NS_ASSERTION(aOpener || !aOriginalOpener, "Shouldn't set mHadOriginalOpener if aOpener is null"); #ifdef DEBUG nsCOMPtr opener = do_QueryInterface(aOpener); MOZ_ASSERT(!opener || opener->IsOuterWindow()); #endif mOpener = do_GetWeakReference(aOpener); NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!"); if (aOriginalOpener) { mHadOriginalOpener = true; } #ifdef DEBUG mSetOpenerWindowCalled = true; #endif } static already_AddRefed TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom) { nsCOMPtr frameLoaderOwner = do_QueryInterface(aFrom); if (!frameLoaderOwner) { return nullptr; } RefPtr frameLoader = frameLoaderOwner->GetFrameLoader(); if (!frameLoader) { return nullptr; } nsCOMPtr target = frameLoader->GetTabChildGlobalAsEventTarget(); return target.forget(); } void nsGlobalWindow::UpdateParentTarget() { // Try to get our frame element's tab child global (its in-process message // manager). If that fails, fall back to the chrome event handler's tab // child global, and if it doesn't have one, just use the chrome event // handler itself. nsCOMPtr frameElement = GetFrameElementInternal(); nsCOMPtr eventTarget = TryGetTabChildGlobalAsEventTarget(frameElement); if (!eventTarget) { nsGlobalWindow* topWin = GetScriptableTopInternal(); if (topWin) { frameElement = topWin->GetFrameElementInternal(); eventTarget = TryGetTabChildGlobalAsEventTarget(frameElement); } } if (!eventTarget) { eventTarget = TryGetTabChildGlobalAsEventTarget(mChromeEventHandler); } if (!eventTarget) { eventTarget = mChromeEventHandler; } mParentTarget = eventTarget; } EventTarget* nsGlobalWindow::GetTargetForDOMEvent() { return GetOuterWindowInternal(); } EventTarget* nsGlobalWindow::GetTargetForEventTargetChain() { return IsInnerWindow() ? this : GetCurrentInnerWindowInternal(); } nsresult nsGlobalWindow::WillHandleEvent(EventChainPostVisitor& aVisitor) { return NS_OK; } JSContext* nsGlobalWindow::GetJSContextForEventHandlers() { return nullptr; } nsresult nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) { NS_PRECONDITION(IsInnerWindow(), "PreHandleEvent is used on outer window!?"); static uint32_t count = 0; EventMessage msg = aVisitor.mEvent->mMessage; aVisitor.mCanHandle = true; aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119 if (msg == eMouseMove && gEntropyCollector) { //Chances are this counter will overflow during the life of the //process, but that's OK for our case. Means we get a little //more entropy. if (count++ % 100 == 0) { //Since the high bits seem to be zero's most of the time, //let's only take the lowest half of the point structure. int16_t myCoord[2]; myCoord[0] = aVisitor.mEvent->refPoint.x; myCoord[1] = aVisitor.mEvent->refPoint.y; gEntropyCollector->RandomUpdate((void*)myCoord, sizeof(myCoord)); gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->time), sizeof(uint32_t)); } } else if (msg == eResize && aVisitor.mEvent->mFlags.mIsTrusted) { // QIing to window so that we can keep the old behavior also in case // a child window is handling resize. nsCOMPtr window = do_QueryInterface(aVisitor.mEvent->originalTarget); if (window) { mIsHandlingResizeEvent = true; } } else if (msg == eMouseDown && aVisitor.mEvent->mFlags.mIsTrusted) { gMouseDown = true; } else if ((msg == eMouseUp || msg == eDragEnd) && aVisitor.mEvent->mFlags.mIsTrusted) { gMouseDown = false; if (gDragServiceDisabled) { nsCOMPtr ds = do_GetService("@mozilla.org/widget/dragservice;1"); if (ds) { gDragServiceDisabled = false; ds->Unsuppress(); } } } aVisitor.mParentTarget = GetParentTarget(); // Handle 'active' event. if (!mIdleObservers.IsEmpty() && aVisitor.mEvent->mFlags.mIsTrusted && (aVisitor.mEvent->HasMouseEventMessage() || aVisitor.mEvent->HasDragEventMessage())) { mAddActiveEventFuzzTime = false; } return NS_OK; } bool nsGlobalWindow::ShouldPromptToBlockDialogs() { MOZ_ASSERT(IsOuterWindow()); nsGlobalWindow *topWindow = GetScriptableTopInternal(); if (!topWindow) { NS_ASSERTION(!mDocShell, "ShouldPromptToBlockDialogs() called without a top window?"); return true; } topWindow = topWindow->GetCurrentInnerWindowInternal(); if (!topWindow) { return true; } return topWindow->DialogsAreBeingAbused(); } bool nsGlobalWindow::AreDialogsEnabled() { MOZ_ASSERT(IsOuterWindow()); nsGlobalWindow *topWindow = GetScriptableTopInternal(); if (!topWindow) { NS_ERROR("AreDialogsEnabled() called without a top window?"); return false; } // TODO: Warn if no top window? topWindow = topWindow->GetCurrentInnerWindowInternal(); if (!topWindow) { return false; } // Dialogs are blocked if the content viewer is hidden if (mDocShell) { nsCOMPtr cv; mDocShell->GetContentViewer(getter_AddRefs(cv)); bool isHidden; cv->GetIsHidden(&isHidden); if (isHidden) { return false; } } return topWindow->mAreDialogsEnabled; } bool nsGlobalWindow::DialogsAreBeingAbused() { MOZ_ASSERT(IsInnerWindow()); NS_ASSERTION(GetScriptableTopInternal() && GetScriptableTopInternal()->GetCurrentInnerWindowInternal() == this, "DialogsAreBeingAbused called with invalid window"); if (mLastDialogQuitTime.IsNull() || nsContentUtils::IsCallerChrome()) { return false; } TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime); if (dialogInterval.ToSeconds() < Preferences::GetInt("dom.successive_dialog_time_limit", DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) { mDialogAbuseCount++; return GetPopupControlState() > openAllowed || mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT; } // Reset the abuse counter mDialogAbuseCount = 0; return false; } bool nsGlobalWindow::ConfirmDialogIfNeeded() { MOZ_ASSERT(IsOuterWindow()); NS_ENSURE_TRUE(mDocShell, false); nsCOMPtr promptSvc = do_GetService("@mozilla.org/embedcomp/prompt-service;1"); if (!promptSvc) { return true; } // Reset popup state while opening a modal dialog, and firing events // about the dialog, to prevent the current state from being active // the whole time a modal dialog is open. nsAutoPopupStatePusher popupStatePusher(openAbused, true); bool disableDialog = false; nsXPIDLString label, title; nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label); nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogPreventTitle", title); promptSvc->Confirm(this, title.get(), label.get(), &disableDialog); if (disableDialog) { DisableDialogs(); return false; } return true; } void nsGlobalWindow::DisableDialogs() { nsGlobalWindow *topWindow = GetScriptableTopInternal(); if (!topWindow) { NS_ERROR("DisableDialogs() called without a top window?"); return; } topWindow = topWindow->GetCurrentInnerWindowInternal(); // TODO: Warn if no top window? if (topWindow) { topWindow->mAreDialogsEnabled = false; } } void nsGlobalWindow::EnableDialogs() { nsGlobalWindow *topWindow = GetScriptableTopInternal(); if (!topWindow) { NS_ERROR("EnableDialogs() called without a top window?"); return; } // TODO: Warn if no top window? topWindow = topWindow->GetCurrentInnerWindowInternal(); if (topWindow) { topWindow->mAreDialogsEnabled = true; } } nsresult nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor) { NS_PRECONDITION(IsInnerWindow(), "PostHandleEvent is used on outer window!?"); // Return early if there is nothing to do. switch (aVisitor.mEvent->mMessage) { case eResize: case eUnload: case eLoad: break; default: return NS_OK; } /* mChromeEventHandler and mContext go dangling in the middle of this function under some circumstances (events that destroy the window) without this addref. */ nsCOMPtr kungFuDeathGrip1(mChromeEventHandler); Unused << kungFuDeathGrip1; nsCOMPtr kungFuDeathGrip2(GetContextInternal()); Unused << kungFuDeathGrip2; if (aVisitor.mEvent->mMessage == eResize) { mIsHandlingResizeEvent = false; } else if (aVisitor.mEvent->mMessage == eUnload && aVisitor.mEvent->mFlags.mIsTrusted) { // Execute bindingdetached handlers before we tear ourselves // down. if (mDoc) { mDoc->BindingManager()->ExecuteDetachedHandlers(); } mIsDocumentLoaded = false; } else if (aVisitor.mEvent->mMessage == eLoad && aVisitor.mEvent->mFlags.mIsTrusted) { // This is page load event since load events don't propagate to |window|. // @see nsDocument::PreHandleEvent. mIsDocumentLoaded = true; nsCOMPtr element = GetFrameElementInternal(); nsIDocShell* docShell = GetDocShell(); if (element && GetParentInternal() && docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { // If we're not in chrome, or at a chrome boundary, fire the // onload event for the frame element. nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(aVisitor.mEvent->mFlags.mIsTrusted, eLoad); event.mFlags.mBubbles = false; // Most of the time we could get a pres context to pass in here, // but not always (i.e. if this window is not shown there won't // be a pres context available). Since we're not firing a GUI // event we don't need a pres context anyway so we just pass // null as the pres context all the time here. EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); } } return NS_OK; } nsresult nsGlobalWindow::DispatchDOMEvent(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent, nsPresContext* aPresContext, nsEventStatus* aEventStatus) { return EventDispatcher::DispatchDOMEvent(static_cast(this), aEvent, aDOMEvent, aPresContext, aEventStatus); } void nsGlobalWindow::PoisonOuterWindowProxy(JSObject *aObject) { MOZ_ASSERT(IsOuterWindow()); if (aObject == GetWrapperPreserveColor()) { PoisonWrapper(); } } nsresult nsGlobalWindow::SetArguments(nsIArray *aArguments) { MOZ_ASSERT(IsOuterWindow()); nsresult rv; // Historically, we've used the same machinery to handle openDialog arguments // (exposed via window.arguments) and showModalDialog arguments (exposed via // window.dialogArguments), even though the former is XUL-only and uses an XPCOM // array while the latter is web-exposed and uses an arbitrary JS value. // Moreover, per-spec |dialogArguments| is a property of the browsing context // (outer), whereas |arguments| lives on the inner. // // We've now mostly separated them, but the difference is still opaque to // nsWindowWatcher (the caller of SetArguments in this little back-and-forth // embedding waltz we do here). // // So we need to demultiplex the two cases here. nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); if (mIsModalContentWindow) { // nsWindowWatcher blindly converts the original nsISupports into an array // of length 1. We need to recover it, and then cast it back to the concrete // object we know it to be. nsCOMPtr supports = do_QueryElementAt(aArguments, 0, &rv); NS_ENSURE_SUCCESS(rv, rv); mDialogArguments = static_cast(supports.get()); } else { mArguments = aArguments; rv = currentInner->DefineArgumentsProperty(aArguments); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsresult nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments) { MOZ_ASSERT(IsInnerWindow()); MOZ_ASSERT(!mIsModalContentWindow); // Handled separately. nsIScriptContext *ctx = GetOuterWindowInternal()->mContext; NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED); AutoJSContext cx; JS::Rooted obj(cx, GetWrapperPreserveColor()); return ctx->SetProperty(obj, "arguments", aArguments); } //***************************************************************************** // nsGlobalWindow::nsIScriptObjectPrincipal //***************************************************************************** nsIPrincipal* nsGlobalWindow::GetPrincipal() { if (mDoc) { // If we have a document, get the principal from the document return mDoc->NodePrincipal(); } if (mDocumentPrincipal) { return mDocumentPrincipal; } // If we don't have a principal and we don't have a document we // ask the parent window for the principal. This can happen when // loading a frameset that has a , in // that case the global window is used in JS before we've loaded // a document into the window. nsCOMPtr objPrincipal = do_QueryInterface(GetParentInternal()); if (objPrincipal) { return objPrincipal->GetPrincipal(); } return nullptr; } //***************************************************************************** // nsGlobalWindow::nsIDOMWindow //***************************************************************************** nsIURI* nsPIDOMWindow::GetDocumentURI() const { return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get(); } nsIURI* nsPIDOMWindow::GetDocBaseURI() const { return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get(); } void nsPIDOMWindow::MaybeCreateDoc() { MOZ_ASSERT(!mDoc); if (nsIDocShell* docShell = GetDocShell()) { // Note that |document| here is the same thing as our mDoc, but we // don't have to explicitly set the member variable because the docshell // has already called SetNewDocument(). nsCOMPtr document = docShell->GetDocument(); Unused << document; } } Element* nsPIDOMWindow::GetFrameElementInternal() const { if (mOuterWindow) { return mOuterWindow->GetFrameElementInternal(); } NS_ASSERTION(!IsInnerWindow(), "GetFrameElementInternal() called on orphan inner window"); return mFrameElement; } void nsPIDOMWindow::SetFrameElementInternal(Element* aFrameElement) { if (IsOuterWindow()) { mFrameElement = aFrameElement; return; } if (!mOuterWindow) { NS_ERROR("frameElement set on inner window with no outer!"); return; } mOuterWindow->SetFrameElementInternal(aFrameElement); } bool nsPIDOMWindow::AddAudioContext(AudioContext* aAudioContext) { MOZ_ASSERT(IsInnerWindow()); mAudioContexts.AppendElement(aAudioContext); // Return true if the context should be muted and false if not. nsIDocShell* docShell = GetDocShell(); return docShell && !docShell->GetAllowMedia() && !aAudioContext->IsOffline(); } void nsPIDOMWindow::RemoveAudioContext(AudioContext* aAudioContext) { MOZ_ASSERT(IsInnerWindow()); mAudioContexts.RemoveElement(aAudioContext); } void nsPIDOMWindow::MuteAudioContexts() { MOZ_ASSERT(IsInnerWindow()); for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { if (!mAudioContexts[i]->IsOffline()) { mAudioContexts[i]->Mute(); } } } void nsPIDOMWindow::UnmuteAudioContexts() { MOZ_ASSERT(IsInnerWindow()); for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { if (!mAudioContexts[i]->IsOffline()) { mAudioContexts[i]->Unmute(); } } } nsGlobalWindow* nsGlobalWindow::Window() { return this; } nsGlobalWindow* nsGlobalWindow::Self() { return this; } Navigator* nsGlobalWindow::GetNavigator(ErrorResult& aError) { MOZ_RELEASE_ASSERT(IsInnerWindow()); if (!mNavigator) { mNavigator = new Navigator(this); } return mNavigator; } nsIDOMNavigator* nsGlobalWindow::GetNavigator() { FORWARD_TO_INNER(GetNavigator, (), nullptr); ErrorResult dummy; nsIDOMNavigator* navigator = GetNavigator(dummy); dummy.SuppressException(); return navigator; } nsScreen* nsGlobalWindow::GetScreen(ErrorResult& aError) { MOZ_RELEASE_ASSERT(IsInnerWindow()); if (!mScreen) { mScreen = nsScreen::Create(this); if (!mScreen) { aError.Throw(NS_ERROR_UNEXPECTED); return nullptr; } } return mScreen; } nsIDOMScreen* nsGlobalWindow::GetScreen() { FORWARD_TO_INNER(GetScreen, (), nullptr); ErrorResult dummy; nsIDOMScreen* screen = GetScreen(dummy); dummy.SuppressException(); return screen; } nsHistory* nsGlobalWindow::GetHistory(ErrorResult& aError) { MOZ_RELEASE_ASSERT(IsInnerWindow()); if (!mHistory) { mHistory = new nsHistory(this); } return mHistory; } nsPerformance* nsPIDOMWindow::GetPerformance() { MOZ_ASSERT(IsInnerWindow()); CreatePerformanceObjectIfNeeded(); return mPerformance; } void nsPIDOMWindow::CreatePerformanceObjectIfNeeded() { MOZ_ASSERT(IsInnerWindow()); if (mPerformance || !mDoc) { return; } RefPtr timing = mDoc->GetNavigationTiming(); nsCOMPtr timedChannel(do_QueryInterface(mDoc->GetChannel())); bool timingEnabled = false; if (!timedChannel || !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) || !timingEnabled) { timedChannel = nullptr; } if (timing) { // If we are dealing with an iframe, we will need the parent's performance // object (so we can add the iframe as a resource of that page). nsPerformance* parentPerformance = nullptr; nsCOMPtr parentWindow = GetScriptableParent(); if (GetOuterWindow() != parentWindow) { if (parentWindow && !parentWindow->IsInnerWindow()) { parentWindow = parentWindow->GetCurrentInnerWindow(); } if (parentWindow) { parentPerformance = parentWindow->GetPerformance(); } } mPerformance = new nsPerformance(this, timing, timedChannel, parentPerformance); } } bool nsPIDOMWindow::GetAudioMuted() const { if (IsInnerWindow()) { return mOuterWindow->GetAudioMuted(); } return mAudioMuted; } void nsPIDOMWindow::SetAudioMuted(bool aMuted) { if (IsInnerWindow()) { mOuterWindow->SetAudioMuted(aMuted); return; } if (mAudioMuted == aMuted) { return; } mAudioMuted = aMuted; RefreshMediaElements(); } float nsPIDOMWindow::GetAudioVolume() const { if (IsInnerWindow()) { return mOuterWindow->GetAudioVolume(); } return mAudioVolume; } nsresult nsPIDOMWindow::SetAudioVolume(float aVolume) { if (IsInnerWindow()) { return mOuterWindow->SetAudioVolume(aVolume); } if (aVolume < 0.0) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } if (mAudioVolume == aVolume) { return NS_OK; } mAudioVolume = aVolume; RefreshMediaElements(); return NS_OK; } void nsPIDOMWindow::RefreshMediaElements() { RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->RefreshAgentsVolume(GetOuterWindow()); } } bool nsPIDOMWindow::GetAudioCaptured() const { MOZ_ASSERT(IsInnerWindow()); return mAudioCaptured; } nsresult nsPIDOMWindow::SetAudioCapture(bool aCapture) { MOZ_ASSERT(IsInnerWindow()); mAudioCaptured = aCapture; RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->RefreshAgentsCapture(GetOuterWindow(), mWindowID); } return NS_OK; } // nsISpeechSynthesisGetter #ifdef MOZ_WEBSPEECH SpeechSynthesis* nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError) { MOZ_RELEASE_ASSERT(IsInnerWindow()); if (!mSpeechSynthesis) { mSpeechSynthesis = new SpeechSynthesis(this); } return mSpeechSynthesis; } #endif already_AddRefed nsGlobalWindow::GetParentOuter() { MOZ_RELEASE_ASSERT(IsOuterWindow()); if (!mDocShell) { return nullptr; } nsCOMPtr parent; if (mDocShell->GetIsBrowserOrApp()) { parent = this; } else { parent = GetParent(); } return parent.forget(); } already_AddRefed nsGlobalWindow::GetParent(ErrorResult& aError) { FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr); } /** * GetScriptableParent is called when script reads window.parent. * * In contrast to GetRealParent, GetScriptableParent respects