зеркало из https://github.com/mozilla/gecko-dev.git
7869 строки
266 KiB
C++
7869 строки
266 KiB
C++
/* -*- 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 <algorithm>
|
|
|
|
#include "mozilla/MemoryReporting.h"
|
|
|
|
// Local Includes
|
|
#include "Navigator.h"
|
|
#include "nsContentSecurityManager.h"
|
|
#include "nsScreen.h"
|
|
#include "nsHistory.h"
|
|
#include "nsDOMNavigationTiming.h"
|
|
#include "nsICookieService.h"
|
|
#include "nsIDOMStorageManager.h"
|
|
#include "nsIPermission.h"
|
|
#include "nsIPermissionManager.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsISecureBrowserUI.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "mozilla/AntiTrackingCommon.h"
|
|
#include "mozilla/dom/ContentFrameMessageManager.h"
|
|
#include "mozilla/dom/EventTarget.h"
|
|
#include "mozilla/dom/LocalStorage.h"
|
|
#include "mozilla/dom/LSObject.h"
|
|
#include "mozilla/dom/Storage.h"
|
|
#include "mozilla/dom/MaybeCrossOriginObject.h"
|
|
#include "mozilla/dom/Performance.h"
|
|
#include "mozilla/dom/StorageEvent.h"
|
|
#include "mozilla/dom/StorageEventBinding.h"
|
|
#include "mozilla/dom/StorageNotifierService.h"
|
|
#include "mozilla/dom/StorageUtils.h"
|
|
#include "mozilla/dom/Timeout.h"
|
|
#include "mozilla/dom/TimeoutHandler.h"
|
|
#include "mozilla/dom/TimeoutManager.h"
|
|
#include "mozilla/dom/WindowProxyHolder.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
# include "mozilla/dom/WindowOrientationObserver.h"
|
|
#endif
|
|
#include "nsBaseCommandController.h"
|
|
#include "nsError.h"
|
|
#include "nsISizeOfEventTarget.h"
|
|
#include "nsDOMJSUtils.h"
|
|
#include "nsArrayUtils.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 "nsISlowScriptDebug.h"
|
|
#include "nsWindowMemoryReporter.h"
|
|
#include "nsWindowSizes.h"
|
|
#include "WindowNamedPropertiesHandler.h"
|
|
#include "nsFrameSelection.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsVariant.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "mozilla/intl/LocaleService.h"
|
|
#include "WindowDestroyedEvent.h"
|
|
#include "nsDocShellLoadState.h"
|
|
#include "mozilla/dom/WindowGlobalChild.h"
|
|
|
|
// Helper Classes
|
|
#include "nsJSUtils.h"
|
|
#include "jsapi.h"
|
|
#include "js/PropertySpec.h"
|
|
#include "js/Wrapper.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsJSEnvironment.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Likely.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
// Other Classes
|
|
#include "mozilla/dom/BarProps.h"
|
|
#include "nsContentCID.h"
|
|
#include "nsLayoutStatics.h"
|
|
#include "nsCCUncollectableMarker.h"
|
|
#include "mozilla/dom/WorkerCommon.h"
|
|
#include "mozilla/dom/ToJSValue.h"
|
|
#include "nsJSPrincipals.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/Components.h"
|
|
#include "mozilla/Debug.h"
|
|
#include "mozilla/EventListenerManager.h"
|
|
#include "mozilla/EventStates.h"
|
|
#include "mozilla/MouseEvents.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/ProcessHangMonitor.h"
|
|
#include "mozilla/StaticPrefs.h"
|
|
#include "mozilla/ThrottledEventQueue.h"
|
|
#include "AudioChannelService.h"
|
|
#include "nsAboutProtocolUtils.h"
|
|
#include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE
|
|
#include "PostMessageEvent.h"
|
|
#include "mozilla/dom/DocGroup.h"
|
|
#include "mozilla/dom/TabGroup.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 "mozilla/dom/Document.h"
|
|
#include "Crypto.h"
|
|
#include "nsDOMString.h"
|
|
#include "nsIEmbeddingSiteWindow.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsIScrollableFrame.h"
|
|
#include "nsView.h"
|
|
#include "nsViewManager.h"
|
|
#include "nsISelectionController.h"
|
|
#include "nsIPrompt.h"
|
|
#include "nsIPromptService.h"
|
|
#include "nsIPromptFactory.h"
|
|
#include "nsIAddonPolicyService.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 "nsDOMCID.h"
|
|
#include "nsDOMWindowUtils.h"
|
|
#include "nsIWindowWatcher.h"
|
|
#include "nsPIWindowWatcher.h"
|
|
#include "nsIContentViewer.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIControllers.h"
|
|
#include "nsGlobalWindowCommands.h"
|
|
#include "nsQueryObject.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsCSSProps.h"
|
|
#include "nsIURIFixup.h"
|
|
#include "nsIURIMutator.h"
|
|
#include "mozilla/EventDispatcher.h"
|
|
#include "mozilla/EventStateManager.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsFocusManager.h"
|
|
#include "nsIXULWindow.h"
|
|
#include "nsITimedChannel.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "mozilla/dom/CustomEvent.h"
|
|
#include "nsIJARChannel.h"
|
|
#include "nsIScreenManager.h"
|
|
#include "nsIEffectiveTLDService.h"
|
|
#include "nsIClassifiedChannel.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"
|
|
|
|
#include "XULDocument.h"
|
|
#include "nsIDOMXULCommandDispatcher.h"
|
|
|
|
#include "nsBindingManager.h"
|
|
#include "nsXBLService.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 "mozilla/dom/IDBFactory.h"
|
|
#include "mozilla/dom/MessageChannel.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
|
|
#include "mozilla/dom/Gamepad.h"
|
|
#include "mozilla/dom/GamepadManager.h"
|
|
|
|
#include "gfxVR.h"
|
|
#include "mozilla/dom/VRDisplay.h"
|
|
#include "mozilla/dom/VRDisplayEvent.h"
|
|
#include "mozilla/dom/VRDisplayEventBinding.h"
|
|
#include "mozilla/dom/VREventObserver.h"
|
|
|
|
#include "nsRefreshDriver.h"
|
|
#include "Layers.h"
|
|
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/dom/Location.h"
|
|
#include "nsHTMLDocument.h"
|
|
#include "nsWrapperCacheInlines.h"
|
|
#include "mozilla/DOMEventTargetHelper.h"
|
|
#include "prrng.h"
|
|
#include "nsSandboxFlags.h"
|
|
#include "nsBaseCommandController.h"
|
|
#include "nsXULControllers.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/IntlUtils.h"
|
|
#include "mozilla/dom/PopStateEvent.h"
|
|
#include "mozilla/dom/PopupBlockedEvent.h"
|
|
#include "mozilla/dom/PrimitiveConversions.h"
|
|
#include "mozilla/dom/WindowBinding.h"
|
|
#include "nsIBrowserChild.h"
|
|
#include "mozilla/dom/MediaQueryList.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/dom/NavigatorBinding.h"
|
|
#include "mozilla/dom/ImageBitmap.h"
|
|
#include "mozilla/dom/ImageBitmapBinding.h"
|
|
#include "mozilla/dom/ServiceWorkerRegistration.h"
|
|
#include "mozilla/dom/U2F.h"
|
|
#include "mozilla/dom/WebIDLGlobalNameHash.h"
|
|
#include "mozilla/dom/Worklet.h"
|
|
#ifdef HAVE_SIDEBAR
|
|
# include "mozilla/dom/ExternalBinding.h"
|
|
#endif
|
|
|
|
#ifdef MOZ_WEBSPEECH
|
|
# include "mozilla/dom/SpeechSynthesis.h"
|
|
#endif
|
|
|
|
// Apple system headers seem to have a check() macro. <sigh>
|
|
#ifdef check
|
|
class nsIScriptTimeoutHandler;
|
|
# undef check
|
|
#endif // check
|
|
#include "AccessCheck.h"
|
|
|
|
#ifdef ANDROID
|
|
# include <android/log.h>
|
|
#endif
|
|
|
|
#ifdef XP_WIN
|
|
# include <process.h>
|
|
# define getpid _getpid
|
|
#else
|
|
# include <unistd.h> // for getpid()
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::dom::ipc;
|
|
using mozilla::BasePrincipal;
|
|
using mozilla::OriginAttributes;
|
|
using mozilla::TimeStamp;
|
|
|
|
#define FORWARD_TO_INNER(method, args, err_rval) \
|
|
PR_BEGIN_MACRO \
|
|
if (!mInnerWindow) { \
|
|
NS_WARNING("No inner window available!"); \
|
|
return err_rval; \
|
|
} \
|
|
return GetCurrentInnerWindowInternal()->method args; \
|
|
PR_END_MACRO
|
|
|
|
#define FORWARD_TO_INNER_VOID(method, args) \
|
|
PR_BEGIN_MACRO \
|
|
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 (!mInnerWindow) { \
|
|
if (mIsClosed) { \
|
|
return err_rval; \
|
|
} \
|
|
nsCOMPtr<Document> kungFuDeathGrip = GetDoc(); \
|
|
::mozilla::Unused << kungFuDeathGrip; \
|
|
if (!mInnerWindow) { \
|
|
return err_rval; \
|
|
} \
|
|
} \
|
|
return GetCurrentInnerWindowInternal()->method args; \
|
|
PR_END_MACRO
|
|
|
|
static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
|
|
extern LazyLogModule gPageCacheLog;
|
|
|
|
static int32_t gOpenPopupSpamCount = 0;
|
|
|
|
static bool gSyncContentBlockingNotifications = false;
|
|
|
|
nsGlobalWindowOuter::OuterWindowByIdTable*
|
|
nsGlobalWindowOuter::sOuterWindowsById = nullptr;
|
|
|
|
/* static */
|
|
nsPIDOMWindowOuter* nsPIDOMWindowOuter::GetFromCurrentInner(
|
|
nsPIDOMWindowInner* aInner) {
|
|
if (!aInner) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsPIDOMWindowOuter* outer = aInner->GetOuterWindow();
|
|
if (!outer || outer->GetCurrentInnerWindow() != aInner) {
|
|
return nullptr;
|
|
}
|
|
|
|
return outer;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsOuterWindowProxy: Outer Window Proxy
|
|
//*****************************************************************************
|
|
|
|
// Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
|
|
// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
|
|
// malloc.
|
|
//
|
|
// We store the nsGlobalWindowOuter* in our first slot.
|
|
//
|
|
// We store our holder weakmap in the second slot.
|
|
const js::Class OuterWindowProxyClass = PROXY_CLASS_DEF(
|
|
"Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */
|
|
|
|
static const size_t OUTER_WINDOW_SLOT = 0;
|
|
static const size_t HOLDER_WEAKMAP_SLOT = 1;
|
|
|
|
class nsOuterWindowProxy : public MaybeCrossOriginObject<js::Wrapper> {
|
|
typedef MaybeCrossOriginObject<js::Wrapper> Base;
|
|
|
|
public:
|
|
constexpr nsOuterWindowProxy() : Base(0) {}
|
|
|
|
bool finalizeInBackground(const JS::Value& priv) const override {
|
|
return false;
|
|
}
|
|
|
|
// Standard internal methods
|
|
/**
|
|
* Implementation of [[GetOwnProperty]] as defined at
|
|
* https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may not be same-compartment
|
|
* with cx.
|
|
*/
|
|
bool getOwnPropertyDescriptor(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
|
|
|
|
/*
|
|
* Implementation of the same-origin case of
|
|
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty>.
|
|
*/
|
|
bool definePropertySameOrigin(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id,
|
|
JS::Handle<JS::PropertyDescriptor> desc,
|
|
JS::ObjectOpResult& result) const override;
|
|
|
|
/**
|
|
* Implementation of [[OwnPropertyKeys]] as defined at
|
|
*
|
|
* https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may not be same-compartment
|
|
* with cx.
|
|
*/
|
|
bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::MutableHandleVector<jsid> props) const override;
|
|
/**
|
|
* Implementation of [[Delete]] as defined at
|
|
* https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may not be same-compartment
|
|
* with cx.
|
|
*/
|
|
bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
JS::ObjectOpResult& result) const override;
|
|
|
|
/**
|
|
* Implementaton of hook for superclass getPrototype() method.
|
|
*/
|
|
JSObject* getSameOriginPrototype(JSContext* cx) const override;
|
|
|
|
/**
|
|
* Implementation of [[HasProperty]] internal method as defined at
|
|
* https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may not be same-compartment
|
|
* with cx.
|
|
*
|
|
* Note that the HTML spec does not define an override for this internal
|
|
* method, so we just want the "normal object" behavior. We have to override
|
|
* it, because js::Wrapper also overrides, with "not normal" behavior.
|
|
*/
|
|
bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
bool* bp) const override;
|
|
|
|
/**
|
|
* Implementation of [[Get]] internal method as defined at
|
|
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get>.
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may or may not be
|
|
* same-compartment with "cx".
|
|
*
|
|
* "receiver" is the receiver ("this") for the get. It will be
|
|
* same-compartment with "cx".
|
|
*
|
|
* "vp" is the return value. It will be same-compartment with "cx".
|
|
*/
|
|
bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
|
|
JS::MutableHandle<JS::Value> vp) const override;
|
|
|
|
/**
|
|
* Implementation of [[Set]] internal method as defined at
|
|
* <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set>.
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may or may not be
|
|
* same-compartment with "cx".
|
|
*
|
|
* "v" is the value being set. It will be same-compartment with "cx".
|
|
*
|
|
* "receiver" is the receiver ("this") for the set. It will be
|
|
* same-compartment with "cx".
|
|
*/
|
|
bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
|
|
JS::ObjectOpResult& result) const override;
|
|
|
|
// SpiderMonkey extensions
|
|
/**
|
|
* Implementation of SpiderMonkey extension which just checks whether this
|
|
* object has the property. Basically Object.getOwnPropertyDescriptor(obj,
|
|
* prop) !== undefined. but does not require reifying the descriptor.
|
|
*
|
|
* We have to override this because js::Wrapper overrides it, but we want
|
|
* different behavior from js::Wrapper.
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may not be same-compartment
|
|
* with cx.
|
|
*/
|
|
bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
bool* bp) const override;
|
|
|
|
/**
|
|
* Implementation of SpiderMonkey extension which is used as a fast path for
|
|
* enumerating.
|
|
*
|
|
* We have to override this because js::Wrapper overrides it, but we want
|
|
* different behavior from js::Wrapper.
|
|
*
|
|
* "proxy" is the WindowProxy object involved. It may not be same-compartment
|
|
* with cx.
|
|
*/
|
|
bool getOwnEnumerablePropertyKeys(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::MutableHandleVector<jsid> props) const override;
|
|
|
|
/**
|
|
* Hook used by SpiderMonkey to implement Object.prototype.toString.
|
|
*/
|
|
const char* className(JSContext* cx,
|
|
JS::Handle<JSObject*> wrapper) const override;
|
|
|
|
void finalize(JSFreeOp* fop, JSObject* proxy) const override;
|
|
size_t objectMoved(JSObject* proxy, JSObject* old) const override;
|
|
|
|
bool isCallable(JSObject* obj) const override { return false; }
|
|
bool isConstructor(JSObject* obj) const override { return false; }
|
|
|
|
static const nsOuterWindowProxy singleton;
|
|
|
|
protected:
|
|
static nsGlobalWindowOuter* GetOuterWindow(JSObject* proxy) {
|
|
nsGlobalWindowOuter* outerWindow =
|
|
nsGlobalWindowOuter::FromSupports(static_cast<nsISupports*>(
|
|
js::GetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT).toPrivate()));
|
|
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<JSObject*> proxy,
|
|
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
|
|
bool& found) const;
|
|
|
|
// Returns a non-null window only if id is an index and we have a
|
|
// window at that index.
|
|
Nullable<WindowProxyHolder> GetSubframeWindow(JSContext* cx,
|
|
JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id) const;
|
|
|
|
bool AppendIndexedPropertyNames(JSObject* proxy,
|
|
JS::MutableHandleVector<jsid> props) const;
|
|
|
|
using MaybeCrossOriginObjectMixins::EnsureHolder;
|
|
bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::MutableHandle<JSObject*> holder) const override;
|
|
};
|
|
|
|
const char* nsOuterWindowProxy::className(JSContext* cx,
|
|
JS::Handle<JSObject*> proxy) const {
|
|
MOZ_ASSERT(js::IsProxy(proxy));
|
|
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return "Object";
|
|
}
|
|
|
|
return "Window";
|
|
}
|
|
|
|
void nsOuterWindowProxy::finalize(JSFreeOp* fop, JSObject* proxy) const {
|
|
nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy);
|
|
if (outerWindow) {
|
|
outerWindow->ClearWrapper(proxy);
|
|
BrowsingContext* bc = outerWindow->GetBrowsingContext();
|
|
if (bc) {
|
|
bc->ClearWindowProxy();
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* IsNonConfigurableReadonlyPrimitiveGlobalProp returns true for
|
|
* property names that fit the following criteria:
|
|
*
|
|
* 1) The ES spec defines a property with that name on globals.
|
|
* 2) The property is non-configurable.
|
|
* 3) The property is non-writable (readonly).
|
|
* 4) The value of the property is a primitive (so doesn't change
|
|
* observably on when navigation happens).
|
|
*
|
|
* Such properties can act as actual non-configurable properties on a
|
|
* WindowProxy, because they are not affected by navigation.
|
|
*/
|
|
#ifndef RELEASE_OR_BETA
|
|
static bool IsNonConfigurableReadonlyPrimitiveGlobalProp(JSContext* cx,
|
|
JS::Handle<jsid> id) {
|
|
return id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAN) ||
|
|
id == GetJSIDByIndex(cx, XPCJSContext::IDX_UNDEFINED) ||
|
|
id == GetJSIDByIndex(cx, XPCJSContext::IDX_INFINITY);
|
|
}
|
|
#endif
|
|
|
|
bool nsOuterWindowProxy::getOwnPropertyDescriptor(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
JS::MutableHandle<JS::PropertyDescriptor> desc) const {
|
|
// First check for indexed access. This is
|
|
// https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
|
|
// step 2, mostly.
|
|
bool found;
|
|
if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) {
|
|
return false;
|
|
}
|
|
if (found) {
|
|
// Step 2.4.
|
|
FillPropertyDescriptor(desc, proxy, true);
|
|
return true;
|
|
}
|
|
|
|
bool isSameOrigin = IsPlatformObjectSameOrigin(cx, proxy);
|
|
|
|
// If we did not find a subframe, we could still have an indexed property
|
|
// access. In that case we should throw a SecurityError in the cross-origin
|
|
// case.
|
|
if (!isSameOrigin && IsArrayIndex(GetArrayIndexFromId(id))) {
|
|
// Step 2.5.2.
|
|
return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("access"));
|
|
}
|
|
|
|
// Step 2.5.1 is handled via the forwarding to js::Wrapper; it saves us an
|
|
// IsArrayIndex(GetArrayIndexFromId(id)) here. We'll never have a property on
|
|
// the Window whose name is an index, because our defineProperty doesn't pass
|
|
// those on to the Window.
|
|
|
|
// Step 3.
|
|
if (isSameOrigin) {
|
|
// Fall through to js::Wrapper.
|
|
{ // Scope for JSAutoRealm while we are dealing with js::Wrapper.
|
|
// When forwarding to js::Wrapper, we should just enter the Realm of proxy
|
|
// for now. That's what js::Wrapper expects, and since we're same-origin
|
|
// anyway this is not changing any security behavior.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
#ifndef RELEASE_OR_BETA // To be turned on in bug 1496510.
|
|
if (!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
|
|
desc.setConfigurable(true);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Now wrap our descriptor back into the Realm that asked for it.
|
|
return JS_WrapPropertyDescriptor(cx, desc);
|
|
}
|
|
|
|
// Step 4.
|
|
if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
|
|
return false;
|
|
}
|
|
|
|
// Step 5
|
|
if (desc.object()) {
|
|
return true;
|
|
}
|
|
|
|
// Step 6 -- check for named subframes.
|
|
if (JSID_IS_STRING(id)) {
|
|
nsAutoJSString name;
|
|
if (!name.init(cx, JSID_TO_STRING(id))) {
|
|
return false;
|
|
}
|
|
nsGlobalWindowOuter* win = GetOuterWindow(proxy);
|
|
if (RefPtr<BrowsingContext> childDOMWin = win->GetChildWindow(name)) {
|
|
JS::Rooted<JS::Value> childValue(cx);
|
|
if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) {
|
|
return false;
|
|
}
|
|
FillPropertyDescriptor(desc, proxy, childValue,
|
|
/* readonly = */ true,
|
|
/* enumerable = */ false);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// And step 7.
|
|
return CrossOriginPropertyFallback(cx, proxy, id, desc);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::definePropertySameOrigin(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
|
JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
|
|
if (IsArrayIndex(GetArrayIndexFromId(id))) {
|
|
// 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();
|
|
}
|
|
|
|
JS::ObjectOpResult ourResult;
|
|
bool ok = js::Wrapper::defineProperty(cx, proxy, id, desc, ourResult);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
if (!ourResult.ok()) {
|
|
// It's possible that this failed because the page got the existing
|
|
// descriptor (which we force to claim to be configurable) and then tried to
|
|
// redefine the property with the descriptor it got but a different value.
|
|
// We want to allow this case to succeed, so check for it and if we're in
|
|
// that case try again but now with an attempt to define a non-configurable
|
|
// property.
|
|
if (!desc.hasConfigurable() || !desc.configurable()) {
|
|
// The incoming descriptor was not explicitly marked "configurable: true",
|
|
// so it failed for some other reason. Just propagate that reason out.
|
|
result = ourResult;
|
|
return true;
|
|
}
|
|
|
|
JS::Rooted<JS::PropertyDescriptor> existingDesc(cx);
|
|
ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &existingDesc);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
if (!existingDesc.object() || existingDesc.configurable()) {
|
|
// We have no existing property, or its descriptor is already configurable
|
|
// (on the Window itself, where things really can be non-configurable).
|
|
// So we failed for some other reason, which we should propagate out.
|
|
result = ourResult;
|
|
return true;
|
|
}
|
|
|
|
JS::Rooted<JS::PropertyDescriptor> updatedDesc(cx, desc);
|
|
updatedDesc.setConfigurable(false);
|
|
|
|
JS::ObjectOpResult ourNewResult;
|
|
ok = js::Wrapper::defineProperty(cx, proxy, id, updatedDesc, ourNewResult);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
if (!ourNewResult.ok()) {
|
|
// Twiddling the configurable flag didn't help. Just return this failure
|
|
// out to the caller.
|
|
result = ourNewResult;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#ifndef RELEASE_OR_BETA // To be turned on in bug 1496510.
|
|
if (desc.hasConfigurable() && !desc.configurable() &&
|
|
!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) {
|
|
// Give callers a way to detect that they failed to "really" define a
|
|
// non-configurable property.
|
|
result.failCantDefineWindowNonConfigurable();
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
result.succeed();
|
|
return true;
|
|
}
|
|
|
|
bool nsOuterWindowProxy::ownPropertyKeys(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::MutableHandleVector<jsid> props) const {
|
|
// Just our indexed stuff followed by our "normal" own property names.
|
|
if (!AppendIndexedPropertyNames(proxy, props)) {
|
|
return false;
|
|
}
|
|
|
|
if (IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
// When forwarding to js::Wrapper, we should just enter the Realm of proxy
|
|
// for now. That's what js::Wrapper expects, and since we're same-origin
|
|
// anyway this is not changing any security behavior.
|
|
JS::RootedVector<jsid> innerProps(cx);
|
|
{ // Scope for JSAutoRealm so we can mark the ids once we exit it
|
|
JSAutoRealm ar(cx, proxy);
|
|
if (!js::Wrapper::ownPropertyKeys(cx, proxy, &innerProps)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (auto& id : innerProps) {
|
|
JS_MarkCrossZoneId(cx, id);
|
|
}
|
|
return js::AppendUnique(cx, props, innerProps);
|
|
}
|
|
|
|
// In the cross-origin case we purposefully exclude subframe names from the
|
|
// list of property names we report here.
|
|
JS::Rooted<JSObject*> holder(cx);
|
|
if (!EnsureHolder(cx, proxy, &holder)) {
|
|
return false;
|
|
}
|
|
|
|
JS::RootedVector<jsid> crossOriginProps(cx);
|
|
if (!js::GetPropertyKeys(cx, holder,
|
|
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
|
|
&crossOriginProps) ||
|
|
!js::AppendUnique(cx, props, crossOriginProps)) {
|
|
return false;
|
|
}
|
|
|
|
return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id,
|
|
JS::ObjectOpResult& result) const {
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("delete"));
|
|
}
|
|
|
|
if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
|
|
// Fail (which means throw if strict, else return false).
|
|
return result.failCantDeleteWindowElement();
|
|
}
|
|
|
|
if (IsArrayIndex(GetArrayIndexFromId(id))) {
|
|
// Indexed, but not supported. Spec says return true.
|
|
return result.succeed();
|
|
}
|
|
|
|
// We're same-origin, so it should be safe to enter the Realm of "proxy".
|
|
// Let's do that, just in case, to avoid cross-compartment issues in our
|
|
// js::Wrapper caller..
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
return js::Wrapper::delete_(cx, proxy, id, result);
|
|
}
|
|
|
|
JSObject* nsOuterWindowProxy::getSameOriginPrototype(JSContext* cx) const {
|
|
return Window_Binding::GetProtoObjectHandle(cx);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::has(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id, bool* bp) const {
|
|
// We could just directly forward this method to js::BaseProxyHandler, but
|
|
// that involves reifying the actual property descriptor, which might be more
|
|
// work than we have to do for has() on the Window.
|
|
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
// In the cross-origin case we only have own properties. Just call hasOwn
|
|
// directly.
|
|
return hasOwn(cx, proxy, id, bp);
|
|
}
|
|
|
|
if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
|
|
*bp = true;
|
|
return true;
|
|
}
|
|
|
|
// Just to be safe in terms of compartment asserts, enter the Realm of
|
|
// "proxy". We're same-origin with it, so this should be safe.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
return js::Wrapper::has(cx, proxy, id, bp);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id, bool* bp) const {
|
|
// We could just directly forward this method to js::BaseProxyHandler, but
|
|
// that involves reifying the actual property descriptor, which might be more
|
|
// work than we have to do for hasOwn() on the Window.
|
|
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
// Avoiding reifying the property descriptor here would require duplicating
|
|
// a bunch of "is this property exposed cross-origin" logic, which is
|
|
// probably not worth it. Just forward this along to the base
|
|
// implementation.
|
|
//
|
|
// It's very important to not forward this to js::Wrapper, because that will
|
|
// not do the right security and cross-origin checks and will pass through
|
|
// the call to the Window.
|
|
//
|
|
// The BaseProxyHandler code is OK with this happening without entering the
|
|
// compartment of "proxy".
|
|
return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
|
|
}
|
|
|
|
if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
|
|
*bp = true;
|
|
return true;
|
|
}
|
|
|
|
// Just to be safe in terms of compartment asserts, enter the Realm of
|
|
// "proxy". We're same-origin with it, so this should be safe.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
return js::Wrapper::hasOwn(cx, proxy, id, bp);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::get(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<JS::Value> receiver,
|
|
JS::Handle<jsid> id,
|
|
JS::MutableHandle<JS::Value> vp) const {
|
|
if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) &&
|
|
xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
|
|
vp.set(JS::ObjectValue(*proxy));
|
|
return MaybeWrapValue(cx, vp);
|
|
}
|
|
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return CrossOriginGet(cx, proxy, receiver, id, vp);
|
|
}
|
|
|
|
bool found;
|
|
if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
|
|
return false;
|
|
}
|
|
|
|
if (found) {
|
|
return true;
|
|
}
|
|
|
|
{ // Scope for JSAutoRealm
|
|
// Enter "proxy"'s Realm. We're in the same-origin case, so this should be
|
|
// safe.
|
|
JSAutoRealm ar(cx, proxy);
|
|
|
|
JS_MarkCrossZoneId(cx, id);
|
|
|
|
JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
|
|
if (!MaybeWrapValue(cx, &wrappedReceiver)) {
|
|
return false;
|
|
}
|
|
|
|
// Fall through to js::Wrapper.
|
|
if (!js::Wrapper::get(cx, proxy, wrappedReceiver, id, vp)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Make sure our return value is in the caller compartment.
|
|
return MaybeWrapValue(cx, vp);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::set(JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id, JS::Handle<JS::Value> v,
|
|
JS::Handle<JS::Value> receiver,
|
|
JS::ObjectOpResult& result) const {
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return CrossOriginSet(cx, proxy, id, v, receiver, result);
|
|
}
|
|
|
|
if (IsArrayIndex(GetArrayIndexFromId(id))) {
|
|
// 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();
|
|
}
|
|
|
|
// Do the rest in the Realm of "proxy", since we're in the same-origin case.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS::Rooted<JS::Value> wrappedArg(cx, v);
|
|
if (!MaybeWrapValue(cx, &wrappedArg)) {
|
|
return false;
|
|
}
|
|
JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
|
|
if (!MaybeWrapValue(cx, &wrappedReceiver)) {
|
|
return false;
|
|
}
|
|
|
|
JS_MarkCrossZoneId(cx, id);
|
|
|
|
return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::MutableHandleVector<jsid> props) const {
|
|
// We could just stop overring getOwnEnumerablePropertyKeys and let our
|
|
// superclasses deal (by falling back on the BaseProxyHandler implementation
|
|
// that uses a combination of ownPropertyKeys and getOwnPropertyDescriptor to
|
|
// only return the enumerable ones. But maybe there's value in having
|
|
// somewhat faster for-in iteration on Window objects...
|
|
|
|
// Like ownPropertyKeys, our indexed stuff followed by our "normal" enumerable
|
|
// own property names.
|
|
if (!AppendIndexedPropertyNames(proxy, props)) {
|
|
return false;
|
|
}
|
|
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
// All the cross-origin properties other than the indexed props are
|
|
// non-enumerable, so we're done here.
|
|
return true;
|
|
}
|
|
|
|
// When forwarding to js::Wrapper, we should just enter the Realm of proxy
|
|
// for now. That's what js::Wrapper expects, and since we're same-origin
|
|
// anyway this is not changing any security behavior.
|
|
JS::RootedVector<jsid> innerProps(cx);
|
|
{ // Scope for JSAutoRealm so we can mark the ids once we exit it.
|
|
JSAutoRealm ar(cx, proxy);
|
|
if (!js::Wrapper::getOwnEnumerablePropertyKeys(cx, proxy, &innerProps)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (auto& id : innerProps) {
|
|
JS_MarkCrossZoneId(cx, id);
|
|
}
|
|
|
|
return js::AppendUnique(cx, props, innerProps);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::GetSubframeWindow(JSContext* cx,
|
|
JS::Handle<JSObject*> proxy,
|
|
JS::Handle<jsid> id,
|
|
JS::MutableHandle<JS::Value> vp,
|
|
bool& found) const {
|
|
Nullable<WindowProxyHolder> frame = GetSubframeWindow(cx, proxy, id);
|
|
if (frame.IsNull()) {
|
|
found = false;
|
|
return true;
|
|
}
|
|
|
|
found = true;
|
|
return WrapObject(cx, frame.Value(), vp);
|
|
}
|
|
|
|
Nullable<WindowProxyHolder> nsOuterWindowProxy::GetSubframeWindow(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) const {
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (!IsArrayIndex(index)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsGlobalWindowOuter* win = GetOuterWindow(proxy);
|
|
return win->IndexedGetterOuter(index);
|
|
}
|
|
|
|
bool nsOuterWindowProxy::AppendIndexedPropertyNames(
|
|
JSObject* proxy, JS::MutableHandleVector<jsid> 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) {
|
|
if (!props.append(INT_TO_JSID(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool nsOuterWindowProxy::EnsureHolder(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy,
|
|
JS::MutableHandle<JSObject*> holder) const {
|
|
return EnsureHolder(cx, proxy, HOLDER_WEAKMAP_SLOT,
|
|
Window_Binding::sCrossOriginAttributes,
|
|
Window_Binding::sCrossOriginMethods, holder);
|
|
}
|
|
|
|
size_t nsOuterWindowProxy::objectMoved(JSObject* obj, JSObject* old) const {
|
|
nsGlobalWindowOuter* outerWindow = GetOuterWindow(obj);
|
|
if (outerWindow) {
|
|
outerWindow->UpdateWrapper(obj, old);
|
|
BrowsingContext* bc = outerWindow->GetBrowsingContext();
|
|
if (bc) {
|
|
bc->UpdateWindowProxy(obj, old);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const nsOuterWindowProxy nsOuterWindowProxy::singleton;
|
|
|
|
class nsChromeOuterWindowProxy : public nsOuterWindowProxy {
|
|
public:
|
|
constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() {}
|
|
|
|
const char* className(JSContext* cx,
|
|
JS::Handle<JSObject*> wrapper) const override;
|
|
|
|
static const nsChromeOuterWindowProxy singleton;
|
|
};
|
|
|
|
const char* nsChromeOuterWindowProxy::className(
|
|
JSContext* cx, JS::Handle<JSObject*> proxy) const {
|
|
MOZ_ASSERT(js::IsProxy(proxy));
|
|
|
|
return "ChromeWindow";
|
|
}
|
|
|
|
const nsChromeOuterWindowProxy nsChromeOuterWindowProxy::singleton;
|
|
|
|
static JSObject* NewOuterWindowProxy(JSContext* cx,
|
|
JS::Handle<JSObject*> global,
|
|
bool isChrome) {
|
|
MOZ_ASSERT(JS_IsGlobalObject(global));
|
|
|
|
JSAutoRealm ar(cx, 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;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//*** nsGlobalWindowOuter: Object Management
|
|
//*****************************************************************************
|
|
|
|
nsGlobalWindowOuter::nsGlobalWindowOuter(uint64_t aWindowID)
|
|
: nsPIDOMWindowOuter(aWindowID),
|
|
mFullscreen(false),
|
|
mFullscreenMode(false),
|
|
mIsClosed(false),
|
|
mInClose(false),
|
|
mHavePendingClose(false),
|
|
mHadOriginalOpener(false),
|
|
mIsPopupSpam(false),
|
|
mBlockScriptedClosingFlag(false),
|
|
mWasOffline(false),
|
|
mCreatingInnerWindow(false),
|
|
mIsChrome(false),
|
|
mAllowScriptsToClose(false),
|
|
mTopLevelOuterContentWindow(false),
|
|
mHasStorageAccess(false),
|
|
#ifdef DEBUG
|
|
mSerial(0),
|
|
mSetOpenerWindowCalled(false),
|
|
#endif
|
|
mCleanedUp(false),
|
|
#ifdef DEBUG
|
|
mIsValidatingTabGroup(false),
|
|
#endif
|
|
mCanSkipCCGeneration(0),
|
|
mAutoActivateVRDisplayID(0) {
|
|
AssertIsOnMainThread();
|
|
|
|
nsLayoutStatics::AddRef();
|
|
|
|
// Initialize the PRCList (this).
|
|
PR_INIT_CLIST(this);
|
|
|
|
// |this| is an outer window. Outer windows start out frozen and
|
|
// remain frozen until they get an inner window.
|
|
MOZ_ASSERT(IsFrozen());
|
|
|
|
// We could have failed the first time through trying
|
|
// to create the entropy collector, so we should
|
|
// try to get one until we succeed.
|
|
|
|
#ifdef DEBUG
|
|
mSerial = nsContentUtils::InnerOrOuterWindowCreated();
|
|
|
|
if (!PR_GetEnv("MOZ_QUIET")) {
|
|
printf_stderr(
|
|
"++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n",
|
|
nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
|
|
static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
|
|
nullptr);
|
|
}
|
|
#endif
|
|
|
|
MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug,
|
|
("DOMWINDOW %p created outer=nullptr", this));
|
|
|
|
// Add ourselves to the outer windows list.
|
|
MOZ_ASSERT(sOuterWindowsById, "Outer Windows hash table must be created!");
|
|
|
|
// |this| is an outer window, add to the outer windows list.
|
|
MOZ_ASSERT(!sOuterWindowsById->Get(mWindowID),
|
|
"This window shouldn't be in the hash table yet!");
|
|
// We seem to see crashes in release builds because of null
|
|
// |sOuterWindowsById|.
|
|
if (sOuterWindowsById) {
|
|
sOuterWindowsById->Put(mWindowID, this);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
/* static */
|
|
void nsGlobalWindowOuter::AssertIsOnMainThread() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
/* static */
|
|
void nsGlobalWindowOuter::Init() {
|
|
AssertIsOnMainThread();
|
|
|
|
NS_ASSERTION(gDOMLeakPRLogOuter,
|
|
"gDOMLeakPRLogOuter should have been initialized!");
|
|
|
|
sOuterWindowsById = new OuterWindowByIdTable();
|
|
}
|
|
|
|
nsGlobalWindowOuter::~nsGlobalWindowOuter() {
|
|
AssertIsOnMainThread();
|
|
|
|
if (sOuterWindowsById) {
|
|
MOZ_ASSERT(sOuterWindowsById->Get(mWindowID),
|
|
"This window should be in the hash table");
|
|
sOuterWindowsById->Remove(mWindowID);
|
|
}
|
|
|
|
nsContentUtils::InnerOrOuterWindowDestroyed();
|
|
|
|
#ifdef DEBUG
|
|
if (!PR_GetEnv("MOZ_QUIET")) {
|
|
nsAutoCString url;
|
|
if (mLastOpenedURI) {
|
|
url = mLastOpenedURI->GetSpecOrDefault();
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
printf_stderr(
|
|
"--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = "
|
|
"%s]\n",
|
|
nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
|
|
static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
|
|
nullptr, url.get());
|
|
}
|
|
#endif
|
|
|
|
MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug,
|
|
("DOMWINDOW %p destroyed", this));
|
|
|
|
JSObject* proxy = GetWrapperMaybeDead();
|
|
if (proxy) {
|
|
if (mBrowsingContext) {
|
|
mBrowsingContext->ClearWindowProxy();
|
|
}
|
|
js::SetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT,
|
|
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.
|
|
//
|
|
// Our linked list of inner windows both contains (an nsGlobalWindowOuter),
|
|
// and our inner windows (nsGlobalWindowInners). This means that we need to
|
|
// use PRCList*. We can then compare that PRCList* to `this` to see if its an
|
|
// inner or outer window.
|
|
PRCList* w;
|
|
while ((w = PR_LIST_HEAD(this)) != this) {
|
|
PR_REMOVE_AND_INIT_LINK(w);
|
|
}
|
|
|
|
DropOuterWindowDocs();
|
|
|
|
if (mTabGroup) {
|
|
mTabGroup->Leave(this);
|
|
}
|
|
|
|
// Outer windows are always supposed to call CleanUp before letting themselves
|
|
// be destroyed.
|
|
MOZ_ASSERT(mCleanedUp);
|
|
|
|
nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
|
|
if (ac) ac->RemoveWindowAsListener(this);
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->RemoveObserver(this, PERM_CHANGE_NOTIFICATION);
|
|
}
|
|
|
|
nsLayoutStatics::Release();
|
|
}
|
|
|
|
// static
|
|
void nsGlobalWindowOuter::ShutDown() {
|
|
AssertIsOnMainThread();
|
|
|
|
delete sOuterWindowsById;
|
|
sOuterWindowsById = nullptr;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::MaybeForgiveSpamCount() {
|
|
if (IsPopupSpamWindow()) {
|
|
SetIsPopupSpamWindow(false);
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetIsPopupSpamWindow(bool aIsPopupSpam) {
|
|
mIsPopupSpam = aIsPopupSpam;
|
|
if (aIsPopupSpam) {
|
|
++gOpenPopupSpamCount;
|
|
} else {
|
|
--gOpenPopupSpamCount;
|
|
NS_ASSERTION(gOpenPopupSpamCount >= 0,
|
|
"Unbalanced decrement of gOpenPopupSpamCount");
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::DropOuterWindowDocs() {
|
|
MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed());
|
|
mDoc = nullptr;
|
|
mSuspendedDoc = nullptr;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::CleanUp() {
|
|
// Guarantee idempotence.
|
|
if (mCleanedUp) return;
|
|
mCleanedUp = true;
|
|
|
|
StartDying();
|
|
|
|
mWindowUtils = nullptr;
|
|
|
|
ClearControllers();
|
|
|
|
mOpener = nullptr; // Forces Release
|
|
if (mContext) {
|
|
mContext = nullptr; // Forces Release
|
|
}
|
|
mChromeEventHandler = nullptr; // Forces Release
|
|
mParentTarget = nullptr;
|
|
mMessageManager = nullptr;
|
|
|
|
mArguments = nullptr;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ClearControllers() {
|
|
if (mControllers) {
|
|
uint32_t count;
|
|
mControllers->GetControllerCount(&count);
|
|
|
|
while (count--) {
|
|
nsCOMPtr<nsIController> controller;
|
|
mControllers->GetControllerAt(count, getter_AddRefs(controller));
|
|
|
|
nsCOMPtr<nsIControllerContext> context = do_QueryInterface(controller);
|
|
if (context) context->SetCommandContext(nullptr);
|
|
}
|
|
|
|
mControllers = nullptr;
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsISupports
|
|
//*****************************************************************************
|
|
|
|
// QueryInterface implementation for nsGlobalWindowOuter
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowOuter)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
|
|
NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
|
|
NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
|
|
NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
|
|
NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
|
|
NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowOuter)
|
|
NS_INTERFACE_MAP_ENTRY(mozIDOMWindowProxy)
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
|
|
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowOuter)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowOuter)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowOuter)
|
|
if (tmp->IsBlackForCC(false)) {
|
|
if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
|
|
return true;
|
|
}
|
|
tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
|
|
if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
|
|
elm->MarkForCC();
|
|
}
|
|
return true;
|
|
}
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindowOuter)
|
|
return tmp->IsBlackForCC(true);
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindowOuter)
|
|
return tmp->IsBlackForCC(false);
|
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowOuter)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowOuter)
|
|
if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
|
|
char name[512];
|
|
nsAutoCString uri;
|
|
if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
|
|
uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault();
|
|
}
|
|
SprintfLiteral(name, "nsGlobalWindowOuter # %" PRIu64 " outer %s",
|
|
tmp->mWindowID, uri.get());
|
|
cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
|
|
} else {
|
|
NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindowOuter, 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(mLocalStorage)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
|
|
|
|
// Traverse stuff from nsPIDOMWindow
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerForInitialContentBrowser)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
|
|
|
|
tmp->TraverseHostObjectURIs(cb);
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mBrowserDOMWindow)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowOuter)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
|
|
|
|
// Unlink stuff from nsPIDOMWindow
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerForInitialContentBrowser)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
|
|
if (tmp->mBrowsingContext) {
|
|
tmp->mBrowsingContext->ClearWindowProxy();
|
|
tmp->mBrowsingContext = nullptr;
|
|
}
|
|
|
|
tmp->UnlinkHostObjectURIs();
|
|
|
|
if (tmp->IsChromeWindow()) {
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mBrowserDOMWindow)
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowOuter)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
bool nsGlobalWindowOuter::IsBlackForCC(bool aTracingNeeded) {
|
|
if (!nsCCUncollectableMarker::sGeneration) {
|
|
return false;
|
|
}
|
|
|
|
// Unlike most wrappers, the outer window wrapper is not a wrapper for
|
|
// the outer window. Instead, the outer window wrapper holds the inner
|
|
// window binding object, which in turn holds the nsGlobalWindowInner, which
|
|
// has a strong reference to the nsGlobalWindowOuter. We're using the
|
|
// mInnerWindow pointer as a flag for that whole chain.
|
|
return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
|
|
(mInnerWindow && HasKnownLiveWrapper())) &&
|
|
(!aTracingNeeded || HasNothingToTrace(ToSupports(this)));
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsIScriptGlobalObject
|
|
//*****************************************************************************
|
|
|
|
nsresult nsGlobalWindowOuter::EnsureScriptEnvironment() {
|
|
if (GetWrapperPreserveColor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_ASSERTION(!GetCurrentInnerWindowInternal(),
|
|
"No cached wrapper, but we have an inner window?");
|
|
NS_ASSERTION(!mContext, "Will overwrite mContext!");
|
|
|
|
// 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.
|
|
mContext = new nsJSContext(!IsFrame(), this);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIScriptContext* nsGlobalWindowOuter::GetScriptContext() { return mContext; }
|
|
|
|
bool nsGlobalWindowOuter::WouldReuseInnerWindow(Document* aNewDocument) {
|
|
// 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;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
nsCOMPtr<nsIURI> uri;
|
|
NS_GetURIWithoutRef(mDoc->GetDocumentURI(), getter_AddRefs(uri));
|
|
NS_ASSERTION(NS_IsAboutBlank(uri), "How'd this happen?");
|
|
}
|
|
#endif
|
|
|
|
// Great, we're the original document, check for one of the other
|
|
// conditions.
|
|
|
|
if (mDoc == aNewDocument) {
|
|
return true;
|
|
}
|
|
|
|
if (BasePrincipal::Cast(mDoc->NodePrincipal())
|
|
->FastEqualsConsideringDomain(aNewDocument->NodePrincipal())) {
|
|
// The origin is the same.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetInitialPrincipalToSubject(
|
|
nsIContentSecurityPolicy* aCSP) {
|
|
// First, grab the subject principal.
|
|
nsCOMPtr<nsIPrincipal> newWindowPrincipal =
|
|
nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
|
|
|
|
// We should never create windows with an expanded principal.
|
|
// If we have a system principal, make sure we're not using it for a content
|
|
// docshell.
|
|
// NOTE: Please keep this logic in sync with nsWebShellWindow::Initialize().
|
|
if (nsContentUtils::IsExpandedPrincipal(newWindowPrincipal) ||
|
|
(nsContentUtils::IsSystemPrincipal(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. An about:blank page has a
|
|
// NullPrincipal.
|
|
bool isNullPrincipal;
|
|
MOZ_ASSERT(NS_SUCCEEDED(mDoc->NodePrincipal()->GetIsNullPrincipal(
|
|
&isNullPrincipal)) &&
|
|
isNullPrincipal);
|
|
#endif
|
|
}
|
|
|
|
// Use the subject (or system) principal as the storage principal too until
|
|
// the new window finishes navigating and gets a real storage principal.
|
|
GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal,
|
|
newWindowPrincipal, aCSP);
|
|
|
|
if (mDoc) {
|
|
mDoc->SetIsInitialDocument(true);
|
|
}
|
|
|
|
RefPtr<PresShell> presShell = GetDocShell()->GetPresShell();
|
|
if (presShell && !presShell->DidInitialize()) {
|
|
// Ensure that if someone plays with this document they will get
|
|
// layout happening.
|
|
presShell->Initialize();
|
|
}
|
|
}
|
|
|
|
#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
|
|
|
|
explicit WindowStateHolder(nsGlobalWindowInner* aWindow);
|
|
|
|
nsGlobalWindowInner* GetInnerWindow() { return mInnerWindow; }
|
|
|
|
void DidRestoreWindow() {
|
|
mInnerWindow = nullptr;
|
|
mInnerWindowReflector = nullptr;
|
|
}
|
|
|
|
protected:
|
|
~WindowStateHolder();
|
|
|
|
nsGlobalWindowInner* 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<JSObject*> mInnerWindowReflector;
|
|
};
|
|
|
|
NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID)
|
|
|
|
WindowStateHolder::WindowStateHolder(nsGlobalWindowInner* aWindow)
|
|
: mInnerWindow(aWindow),
|
|
mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) {
|
|
MOZ_ASSERT(aWindow, "null window");
|
|
|
|
aWindow->Suspend();
|
|
|
|
// 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)
|
|
|
|
bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument,
|
|
SecureContextFlags aFlags) {
|
|
nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
|
|
if (nsContentUtils::IsSystemPrincipal(principal)) {
|
|
return true;
|
|
}
|
|
|
|
// Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object
|
|
// With some modifications to allow for aFlags.
|
|
|
|
bool hadNonSecureContextCreator = false;
|
|
|
|
nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent();
|
|
MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?");
|
|
if (nsGlobalWindowOuter::Cast(parentOuterWin) != this) {
|
|
// There may be a small chance that parentOuterWin has navigated in
|
|
// the time that it took us to start loading this sub-document. If that
|
|
// were the case then parentOuterWin->GetCurrentInnerWindow() wouldn't
|
|
// return the window for the document that is embedding us. For this
|
|
// reason we only use the GetScriptableParent call above to check that we
|
|
// have a same-type parent, but actually get the inner window via the
|
|
// document that we know is embedding us.
|
|
Document* creatorDoc = aDocument->GetParentDocument();
|
|
if (!creatorDoc) {
|
|
return false; // we must be tearing down
|
|
}
|
|
nsGlobalWindowInner* parentWin =
|
|
nsGlobalWindowInner::Cast(creatorDoc->GetInnerWindow());
|
|
if (!parentWin) {
|
|
return false; // we must be tearing down
|
|
}
|
|
MOZ_ASSERT(parentWin == nsGlobalWindowInner::Cast(
|
|
parentOuterWin->GetCurrentInnerWindow()),
|
|
"Creator window mismatch while setting Secure Context state");
|
|
hadNonSecureContextCreator = !parentWin->IsSecureContext();
|
|
}
|
|
|
|
if (hadNonSecureContextCreator) {
|
|
return false;
|
|
}
|
|
|
|
if (nsContentUtils::HttpsStateIsModern(aDocument)) {
|
|
return true;
|
|
}
|
|
|
|
if (principal->GetIsNullPrincipal()) {
|
|
nsCOMPtr<nsIURI> uri = aDocument->GetOriginalURI();
|
|
// IsOriginPotentiallyTrustworthy doesn't care about origin attributes so
|
|
// it doesn't actually matter what we use here, but reusing the document
|
|
// principal's attributes is convenient.
|
|
const OriginAttributes& attrs = principal->OriginAttributesRef();
|
|
// CreateContentPrincipal correctly gets a useful principal for blob: and
|
|
// other URI_INHERITS_SECURITY_CONTEXT URIs.
|
|
principal = BasePrincipal::CreateContentPrincipal(uri, attrs);
|
|
if (NS_WARN_IF(!principal)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIContentSecurityManager> csm =
|
|
do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
|
|
NS_WARNING_ASSERTION(csm, "csm is null");
|
|
if (csm) {
|
|
bool isTrustworthyOrigin = false;
|
|
csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
|
|
if (isTrustworthyOrigin) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 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_FN("enablePrivilege", EnablePrivilege, 1, 0), JS_FS_END};
|
|
|
|
static bool InitializeLegacyNetscapeObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGlobal) {
|
|
JSAutoRealm ar(aCx, aGlobal);
|
|
|
|
// Note: MathJax depends on window.netscape being exposed. See bug 791526.
|
|
JS::Rooted<JSObject*> 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 (!xpc::IsInAutomation()) {
|
|
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);
|
|
}
|
|
|
|
struct MOZ_STACK_CLASS CompartmentFinderState {
|
|
explicit CompartmentFinderState(nsIPrincipal* aPrincipal)
|
|
: principal(aPrincipal), compartment(nullptr) {}
|
|
|
|
// Input: we look for a compartment which is same-origin with the
|
|
// given principal.
|
|
nsIPrincipal* principal;
|
|
|
|
// Output: We set this member if we find a compartment.
|
|
JS::Compartment* compartment;
|
|
};
|
|
|
|
static JS::CompartmentIterResult FindSameOriginCompartment(
|
|
JSContext* aCx, void* aData, JS::Compartment* aCompartment) {
|
|
auto* data = static_cast<CompartmentFinderState*>(aData);
|
|
MOZ_ASSERT(!data->compartment, "Why are we getting called?");
|
|
|
|
// If this compartment is not safe to share across globals, don't do
|
|
// anything with it; in particular we should not be getting a
|
|
// CompartmentPrivate from such a compartment, because it may be in
|
|
// the middle of being collected and its CompartmentPrivate may no
|
|
// longer be valid.
|
|
if (!js::IsSharableCompartment(aCompartment)) {
|
|
return JS::CompartmentIterResult::KeepGoing;
|
|
}
|
|
|
|
auto* compartmentPrivate = xpc::CompartmentPrivate::Get(aCompartment);
|
|
if (!compartmentPrivate->CanShareCompartmentWith(data->principal)) {
|
|
// Can't reuse this one, keep going.
|
|
return JS::CompartmentIterResult::KeepGoing;
|
|
}
|
|
|
|
// We have a winner!
|
|
data->compartment = aCompartment;
|
|
return JS::CompartmentIterResult::Stop;
|
|
}
|
|
|
|
static JS::RealmCreationOptions& SelectZone(
|
|
JSContext* aCx, nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
|
|
JS::RealmCreationOptions& aOptions) {
|
|
// Use the shared system compartment for chrome windows.
|
|
if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
|
|
return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
|
|
}
|
|
|
|
if (aNewInner->GetOuterWindow()) {
|
|
nsGlobalWindowOuter* top = aNewInner->GetTopInternal();
|
|
if (top == aNewInner->GetOuterWindow()) {
|
|
// We're a toplevel load. Use a new zone. This way, when we do
|
|
// zone-based compartment sharing we won't share compartments
|
|
// across navigations.
|
|
return aOptions.setNewCompartmentAndZone();
|
|
}
|
|
|
|
// If we have a top-level window, use its zone.
|
|
if (top && top->GetGlobalJSObject()) {
|
|
JS::Zone* zone = JS::GetObjectZone(top->GetGlobalJSObject());
|
|
// Now try to find an existing compartment that's same-origin
|
|
// with our principal.
|
|
CompartmentFinderState data(aPrincipal);
|
|
JS_IterateCompartmentsInZone(aCx, zone, &data, FindSameOriginCompartment);
|
|
if (data.compartment) {
|
|
return aOptions.setExistingCompartment(data.compartment);
|
|
}
|
|
return aOptions.setNewCompartmentInExistingZone(top->GetGlobalJSObject());
|
|
}
|
|
}
|
|
|
|
return aOptions.setNewCompartmentAndZone();
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
nsGlobalWindowInner* aNewInner,
|
|
nsIURI* aURI,
|
|
nsIPrincipal* aPrincipal,
|
|
JS::MutableHandle<JSObject*> aGlobal,
|
|
bool aIsSecureContext) {
|
|
MOZ_ASSERT(aCx);
|
|
MOZ_ASSERT(aNewInner);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
// DOMWindow with nsEP is not supported, we have to make sure
|
|
// no one creates one accidentally.
|
|
nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal);
|
|
MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
|
|
|
|
JS::RealmOptions options;
|
|
|
|
SelectZone(aCx, aPrincipal, aNewInner, options.creationOptions());
|
|
|
|
options.creationOptions().setSecureContext(aIsSecureContext);
|
|
|
|
xpc::InitGlobalObjectOptions(options, aPrincipal);
|
|
|
|
// Determine if we need the Components object.
|
|
bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) ||
|
|
TreatAsRemoteXUL(aPrincipal);
|
|
uint32_t flags = needComponents ? 0 : xpc::OMIT_COMPONENTS_OBJECT;
|
|
flags |= xpc::DONT_FIRE_ONNEWGLOBALHOOK;
|
|
|
|
if (!Window_Binding::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 nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
|
|
nsISupports* aState,
|
|
bool aForceReuseInnerWindow) {
|
|
MOZ_ASSERT(mDocumentPrincipal == nullptr,
|
|
"mDocumentPrincipal prematurely set!");
|
|
MOZ_ASSERT(mDocumentStoragePrincipal == nullptr,
|
|
"mDocumentStoragePrincipal prematurely set!");
|
|
MOZ_ASSERT(aDocument);
|
|
|
|
// Bail out early if we're in process of closing down the window.
|
|
NS_ENSURE_STATE(!mCleanedUp);
|
|
|
|
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;
|
|
}
|
|
|
|
RefPtr<Document> oldDoc = mDoc;
|
|
MOZ_RELEASE_ASSERT(oldDoc != aDocument);
|
|
|
|
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 (!js::CheckRecursionLimitConservativeDontReport(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.
|
|
nsPIDOMWindowOuter* privateRoot = GetPrivateRoot();
|
|
|
|
if (privateRoot == 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(
|
|
NewRunnableMethod("nsGlobalWindowOuter::ClearStatus", this,
|
|
&nsGlobalWindowOuter::ClearStatus));
|
|
|
|
// Sometimes, WouldReuseInnerWindow() returns true even if there's no inner
|
|
// window (see bug 776497). Be safe.
|
|
bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) &&
|
|
GetCurrentInnerWindowInternal();
|
|
|
|
nsresult rv;
|
|
|
|
// 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
|
|
|
|
RefPtr<nsGlobalWindowInner> currentInner = GetCurrentInnerWindowInternal();
|
|
|
|
if (currentInner && currentInner->mNavigator) {
|
|
currentInner->mNavigator->OnNavigation();
|
|
}
|
|
|
|
RefPtr<nsGlobalWindowInner> newInnerWindow;
|
|
bool createdInnerWindow = false;
|
|
|
|
bool thisChrome = IsChromeWindow();
|
|
|
|
nsCOMPtr<WindowStateHolder> wsh = do_QueryInterface(aState);
|
|
NS_ASSERTION(!aState || wsh,
|
|
"What kind of weird state are you giving me here?");
|
|
|
|
bool doomCurrentInner = false;
|
|
|
|
// Only non-gray (i.e. exposed to JS) objects should be assigned to
|
|
// newInnerGlobal.
|
|
JS::Rooted<JSObject*> 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->GetWrapper();
|
|
|
|
// 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<JSObject*> rootedObject(cx, GetWrapper());
|
|
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
|
|
// realm to match the new document. NB: We don't just call
|
|
// currentInner->RefreshRealmPrincipals() here because we haven't yet set
|
|
// its mDoc to aDocument.
|
|
JS::Realm* realm = js::GetNonCCWObjectRealm(newInnerGlobal);
|
|
#ifdef DEBUG
|
|
bool sameOrigin = false;
|
|
nsIPrincipal* existing = nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
|
|
aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
|
|
MOZ_ASSERT(sameOrigin);
|
|
#endif
|
|
JS::SetRealmPrincipals(realm,
|
|
nsJSPrincipals::get(aDocument->NodePrincipal()));
|
|
} else {
|
|
if (aState) {
|
|
newInnerWindow = wsh->GetInnerWindow();
|
|
newInnerGlobal = newInnerWindow->GetWrapper();
|
|
} else {
|
|
newInnerWindow = nsGlobalWindowInner::Create(this, thisChrome);
|
|
if (StaticPrefs::dom_timeout_defer_during_load()) {
|
|
// ensure the initial loading state is known
|
|
newInnerWindow->SetActiveLoadingState(
|
|
aDocument->GetReadyStateEnum() ==
|
|
Document::ReadyState::READYSTATE_LOADING);
|
|
}
|
|
|
|
// The outer window is automatically treated as frozen when we
|
|
// null out the inner window. As a result, initializing classes
|
|
// on the new inner won'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;
|
|
|
|
mCreatingInnerWindow = true;
|
|
// Every script context we are initialized with must create a
|
|
// new global.
|
|
rv = CreateNativeGlobalForInner(
|
|
cx, newInnerWindow, aDocument->GetDocumentURI(),
|
|
aDocument->NodePrincipal(), &newInnerGlobal,
|
|
ComputeIsSecureContext(aDocument));
|
|
NS_ASSERTION(
|
|
NS_SUCCEEDED(rv) && newInnerGlobal &&
|
|
newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal,
|
|
"Failed to get script global");
|
|
|
|
mCreatingInnerWindow = false;
|
|
createdInnerWindow = true;
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
if (currentInner && currentInner->GetWrapperPreserveColor()) {
|
|
// Don't free objects on our current inner window if it's going to be
|
|
// held in the bfcache.
|
|
if (!currentInner->IsFrozen()) {
|
|
doomCurrentInner = true;
|
|
}
|
|
}
|
|
|
|
mInnerWindow = newInnerWindow;
|
|
MOZ_ASSERT(mInnerWindow);
|
|
mInnerWindow->TryToCacheTopInnerWindow();
|
|
|
|
if (!GetWrapperPreserveColor()) {
|
|
JS::Rooted<JSObject*> outer(
|
|
cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
|
|
NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);
|
|
|
|
js::SetProxyReservedSlot(outer, OUTER_WINDOW_SLOT,
|
|
js::PrivateValue(ToSupports(this)));
|
|
|
|
// Inform the nsJSContext, which is the canonical holder of the outer.
|
|
mContext->SetWindowProxy(outer);
|
|
|
|
SetWrapper(mContext->GetWindowProxy());
|
|
} else {
|
|
JS::Rooted<JSObject*> outerObject(
|
|
cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
|
|
if (!outerObject) {
|
|
NS_ERROR("out of memory");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> obj(cx, GetWrapper());
|
|
|
|
MOZ_ASSERT(js::IsWindowProxy(obj));
|
|
|
|
js::SetProxyReservedSlot(obj, OUTER_WINDOW_SLOT,
|
|
js::PrivateValue(nullptr));
|
|
js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
|
|
js::PrivateValue(nullptr));
|
|
js::SetProxyReservedSlot(obj, HOLDER_WEAKMAP_SLOT, JS::UndefinedValue());
|
|
|
|
outerObject = xpc::TransplantObject(cx, obj, outerObject);
|
|
if (!outerObject) {
|
|
mBrowsingContext->ClearWindowProxy();
|
|
NS_ERROR("unable to transplant wrappers, probably OOM");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
|
|
js::PrivateValue(ToSupports(this)));
|
|
|
|
SetWrapper(outerObject);
|
|
|
|
MOZ_ASSERT(JS::GetNonCCWObjectGlobal(outerObject) == newInnerGlobal);
|
|
|
|
// Inform the nsJSContext, which is the canonical holder of the outer.
|
|
mContext->SetWindowProxy(outerObject);
|
|
}
|
|
|
|
// Enter the new global's realm.
|
|
JSAutoRealm ar(cx, GetWrapperPreserveColor());
|
|
|
|
{
|
|
JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor());
|
|
js::SetWindowProxy(cx, newInnerGlobal, outer);
|
|
mBrowsingContext->SetWindowProxy(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
|
|
// nsGlobalWindowOuter, so GetWrapperPreserveColor() on that outer
|
|
// nsGlobalWindowOuter doesn't return null and
|
|
// nsGlobalWindowOuter::OuterObject works correctly.
|
|
JS::Rooted<JS::Value> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
JSAutoRealm ar(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<JSObject*> rootedJSObject(cx, GetWrapperPreserveColor());
|
|
JS::Rooted<JSObject*> 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
|
|
|
|
mInnerWindow->SyncStateFromParentWindow();
|
|
}
|
|
|
|
// Add an extra ref in case we release mContext during GC.
|
|
nsCOMPtr<nsIScriptContext> kungFuDeathGrip(mContext);
|
|
|
|
aDocument->SetScriptGlobalObject(newInnerWindow);
|
|
MOZ_ASSERT(newInnerWindow->mTabGroup,
|
|
"We must have a TabGroup cached at this point");
|
|
|
|
if (!aState) {
|
|
if (reUseInnerWindow) {
|
|
MOZ_RELEASE_ASSERT(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->mPerformance = nullptr;
|
|
|
|
// This must be called after nullifying the internal objects because
|
|
// here we could recreate them, calling the getter methods, and store
|
|
// them into the JS slots. If we nullify them after, the slot values and
|
|
// the objects will be out of sync.
|
|
newInnerWindow->ClearDocumentDependentSlots(cx);
|
|
|
|
// When replacing an initial about:blank document we call
|
|
// ExecutionReady again to update the client creation URL.
|
|
rv = newInnerWindow->ExecutionReady();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
newInnerWindow->InnerSetNewDocument(cx, aDocument);
|
|
|
|
// Initialize DOM classes etc on the inner window.
|
|
JS::Rooted<JSObject*> obj(cx, newInnerGlobal);
|
|
rv = kungFuDeathGrip->InitClasses(obj);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = newInnerWindow->ExecutionReady();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Tell the WindowGlobalParent that it should become the current window global
|
|
// for our BrowsingContext if it isn't already.
|
|
mInnerWindow->GetWindowGlobalChild()->SendUpdateDocumentURI(
|
|
aDocument->GetDocumentURI());
|
|
mInnerWindow->GetWindowGlobalChild()->SendBecomeCurrentWindowGlobal();
|
|
|
|
// We no longer need the old inner window. Start its destruction if
|
|
// its not being reused and clear our reference.
|
|
if (doomCurrentInner) {
|
|
currentInner->FreeInnerObjects();
|
|
}
|
|
currentInner = nullptr;
|
|
|
|
// Ask the JS engine to assert that it's valid to access our DocGroup whenever
|
|
// it runs JS code for this realm. We skip the check if this window is for
|
|
// chrome JS or an add-on.
|
|
nsCOMPtr<nsIPrincipal> principal = mDoc->NodePrincipal();
|
|
if (GetDocGroup() && !nsContentUtils::IsSystemPrincipal(principal) &&
|
|
!BasePrincipal::Cast(principal)->AddonPolicy()) {
|
|
js::SetRealmValidAccessPtr(
|
|
cx, newInnerGlobal, newInnerWindow->GetDocGroup()->GetValidAccessPtr());
|
|
}
|
|
|
|
// 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(NewRunnableMethod(
|
|
"nsGlobalWindowInner::FireOnNewGlobalObject", newInnerWindow,
|
|
&nsGlobalWindowInner::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(NewRunnableMethod(
|
|
"nsGlobalWindowOuter::DispatchDOMWindowCreated", this,
|
|
&nsGlobalWindowOuter::DispatchDOMWindowCreated));
|
|
}
|
|
}
|
|
|
|
PreloadLocalStorage();
|
|
|
|
// If we have a recorded interesting Large-Allocation header status, report it
|
|
// to the newly attached document.
|
|
ReportLargeAllocStatus();
|
|
mLargeAllocStatus = LargeAllocStatus::NONE;
|
|
|
|
mHasStorageAccess = false;
|
|
nsIURI* uri = aDocument->GetDocumentURI();
|
|
if (newInnerWindow &&
|
|
aDocument->CookieSettings()->GetRejectThirdPartyTrackers() &&
|
|
nsContentUtils::IsThirdPartyWindowOrChannel(newInnerWindow, nullptr,
|
|
uri) &&
|
|
nsContentUtils::IsTrackingResourceWindow(newInnerWindow)) {
|
|
// Grant storage access by default if the first-party storage access
|
|
// permission has been granted already.
|
|
// Don't notify in this case, since we would be notifying the user
|
|
// needlessly.
|
|
mHasStorageAccess = AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
newInnerWindow, uri, nullptr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::PreloadLocalStorage() {
|
|
if (!Storage::StoragePrefIsEnabled()) {
|
|
return;
|
|
}
|
|
|
|
if (IsChromeWindow()) {
|
|
return;
|
|
}
|
|
|
|
nsIPrincipal* principal = GetPrincipal();
|
|
nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
|
|
if (!principal || !storagePrincipal) {
|
|
return;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIDOMStorageManager> storageManager =
|
|
do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
// private browsing windows do not persist local storage to disk so we should
|
|
// only try to precache storage when we're not a private browsing window.
|
|
if (principal->GetPrivateBrowsingId() == 0) {
|
|
RefPtr<Storage> storage;
|
|
rv = storageManager->PrecacheStorage(principal, storagePrincipal,
|
|
getter_AddRefs(storage));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mLocalStorage = storage;
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
|
|
if (!mDoc) {
|
|
return;
|
|
}
|
|
|
|
// Fire DOMWindowCreated at chrome event listeners
|
|
nsContentUtils::DispatchChromeEvent(mDoc, ToSupports(mDoc),
|
|
NS_LITERAL_STRING("DOMWindowCreated"),
|
|
CanBubble::eYes, Cancelable::eNo);
|
|
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
|
|
// The event dispatching could possibly cause docshell destory, and
|
|
// consequently cause mDoc to be set to nullptr by DropOuterWindowDocs(),
|
|
// so check it again here.
|
|
if (observerService && mDoc) {
|
|
nsAutoString origin;
|
|
nsIPrincipal* principal = mDoc->NodePrincipal();
|
|
nsContentUtils::GetUTFOrigin(principal, origin);
|
|
observerService->NotifyObservers(
|
|
static_cast<nsIDOMWindow*>(this),
|
|
nsContentUtils::IsSystemPrincipal(principal)
|
|
? "chrome-document-global-created"
|
|
: "content-document-global-created",
|
|
origin.get());
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ClearStatus() { SetStatusOuter(EmptyString()); }
|
|
|
|
void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) {
|
|
MOZ_ASSERT(aDocShell);
|
|
|
|
if (aDocShell == mDocShell) {
|
|
return;
|
|
}
|
|
|
|
mDocShell = aDocShell;
|
|
mBrowsingContext = aDocShell->GetBrowsingContext();
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
|
|
MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup ||
|
|
mTabGroup ==
|
|
nsGlobalWindowOuter::Cast(parentWindow)->mTabGroup);
|
|
|
|
mTopLevelOuterContentWindow =
|
|
!mIsChrome && GetScriptableTopInternal() == this;
|
|
|
|
// Get our enclosing chrome shell and retrieve its global window impl, so
|
|
// that we can do some forwarding to the chrome document.
|
|
RefPtr<EventTarget> chromeEventHandler;
|
|
mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
|
|
mChromeEventHandler = 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<nsPIDOMWindowOuter> parentWindow = GetParent();
|
|
if (parentWindow.get() != this) {
|
|
mChromeEventHandler = parentWindow->GetChromeEventHandler();
|
|
} else {
|
|
mChromeEventHandler = NS_NewWindowRoot(this);
|
|
mIsRootOuterWindow = true;
|
|
}
|
|
}
|
|
|
|
bool docShellActive;
|
|
mDocShell->GetIsActive(&docShellActive);
|
|
SetIsBackgroundInternal(!docShellActive);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::DetachFromDocShell() {
|
|
// 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).
|
|
|
|
if (mDoc && DocGroup::TryToLoadIframesInBackground()) {
|
|
DocGroup* docGroup = GetDocGroup();
|
|
RefPtr<nsIDocShell> docShell = GetDocShell();
|
|
RefPtr<nsDocShell> dShell = nsDocShell::Cast(docShell);
|
|
if (dShell) {
|
|
docGroup->TryFlushIframePostMessages(dShell->GetOuterWindowID());
|
|
}
|
|
}
|
|
|
|
// Call FreeInnerObjects on all inner windows, not just the current
|
|
// one, since some could be held by WindowStateHolder objects that
|
|
// are GC-owned.
|
|
RefPtr<nsGlobalWindowInner> inner;
|
|
for (PRCList* node = PR_LIST_HEAD(this); node != this;
|
|
node = PR_NEXT_LINK(inner)) {
|
|
// This cast is safe because `node != this`. Non-this nodes are inner
|
|
// windows.
|
|
inner = static_cast<nsGlobalWindowInner*>(node);
|
|
MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == this);
|
|
inner->FreeInnerObjects();
|
|
}
|
|
|
|
// Don't report that we were detached to the nsWindowMemoryReporter, as it
|
|
// only tracks inner windows.
|
|
|
|
NotifyWindowIDDestroyed("outer-window-destroyed");
|
|
|
|
nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();
|
|
|
|
if (currentInner) {
|
|
NS_ASSERTION(mDoc, "Must have doc!");
|
|
|
|
// Remember the document's principal and URI.
|
|
mDocumentPrincipal = mDoc->NodePrincipal();
|
|
mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal();
|
|
mDocumentURI = mDoc->GetDocumentURI();
|
|
|
|
// Release our document reference
|
|
DropOuterWindowDocs();
|
|
}
|
|
|
|
ClearControllers();
|
|
|
|
mChromeEventHandler = nullptr; // force release now
|
|
|
|
if (mContext) {
|
|
// When we're about to destroy a top level content window
|
|
// (for example a tab), we trigger a full GC by passing null as the last
|
|
// param. We also trigger a full GC for chrome windows.
|
|
nsJSContext::PokeGC(JS::GCReason::SET_DOC_SHELL,
|
|
(mTopLevelOuterContentWindow || mIsChrome)
|
|
? nullptr
|
|
: GetWrapperPreserveColor());
|
|
mContext = nullptr;
|
|
}
|
|
|
|
mDocShell = nullptr;
|
|
mBrowsingContext->ClearDocShell();
|
|
|
|
MaybeForgiveSpamCount();
|
|
CleanUp();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
|
|
bool aOriginalOpener) {
|
|
nsWeakPtr opener = do_GetWeakReference(aOpener);
|
|
if (opener == mOpener) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!aOpener || !aOpener->GetDocShell() ||
|
|
(GetBrowsingContext() &&
|
|
aOpener->GetBrowsingContext() &&
|
|
aOpener->GetBrowsingContext()->Id() ==
|
|
GetBrowsingContext()->GetOpenerId()));
|
|
return;
|
|
}
|
|
|
|
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");
|
|
|
|
mOpener = opener.forget();
|
|
NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!");
|
|
|
|
if (mDocShell) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
!aOriginalOpener || !aOpener ||
|
|
// TODO(farre): Allowing to set a closed or closing window as
|
|
// opener is not ideal, since it won't have a docshell and
|
|
// therefore no browsing context. This means that we're
|
|
// effectively setting the browsing context opener to null and
|
|
// the window opener to a closed window. This needs to be
|
|
// cleaned up, see Bug 1511353.
|
|
nsGlobalWindowOuter::Cast(aOpener)->IsClosedOrClosing() ||
|
|
// TODO(farre): Allowing to set an opener on a closed window is
|
|
// not ideal either, but we need to allow it for now. Bug 1543056.
|
|
IsClosedOrClosing() ||
|
|
aOpener->GetBrowsingContext()->Id() ==
|
|
GetBrowsingContext()->GetOpenerId());
|
|
// TODO(farre): Here we really wish to only consider the case
|
|
// where 'aOriginalOpener'. See bug 1509016.
|
|
GetBrowsingContext()->SetOpener(aOpener ? aOpener->GetBrowsingContext()
|
|
: nullptr);
|
|
}
|
|
|
|
// Check that the js visible opener matches! We currently don't depend on this
|
|
// being true outside of nightly, so we disable the assertion in optimized
|
|
// release / beta builds.
|
|
nsPIDOMWindowOuter* contentOpener = GetSanitizedOpener(aOpener);
|
|
|
|
// contentOpener is not used when the DIAGNOSTIC_ASSERT is compiled out.
|
|
mozilla::Unused << contentOpener;
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
!contentOpener || !mTabGroup ||
|
|
mTabGroup == nsGlobalWindowOuter::Cast(contentOpener)->mTabGroup);
|
|
|
|
if (aOriginalOpener) {
|
|
MOZ_ASSERT(!mHadOriginalOpener,
|
|
"Probably too late to call ComputeIsSecureContext again");
|
|
mHadOriginalOpener = true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
mSetOpenerWindowCalled = true;
|
|
#endif
|
|
}
|
|
|
|
void nsGlobalWindowOuter::UpdateParentTarget() {
|
|
// NOTE: This method is nearly identical to
|
|
// nsGlobalWindowInner::UpdateParentTarget(). IF YOU UPDATE THIS METHOD,
|
|
// UPDATE THE OTHER ONE TOO! The one difference is that this method updates
|
|
// mMessageManager as well, which inner windows don't have.
|
|
|
|
// 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<Element> frameElement = GetFrameElementInternal();
|
|
mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(frameElement);
|
|
|
|
if (!mMessageManager) {
|
|
nsGlobalWindowOuter* topWin = GetScriptableTopInternal();
|
|
if (topWin) {
|
|
frameElement = topWin->GetFrameElementInternal();
|
|
mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(frameElement);
|
|
}
|
|
}
|
|
|
|
if (!mMessageManager) {
|
|
mMessageManager =
|
|
nsContentUtils::TryGetBrowserChildGlobal(mChromeEventHandler);
|
|
}
|
|
|
|
if (mMessageManager) {
|
|
mParentTarget = mMessageManager;
|
|
} else {
|
|
mParentTarget = mChromeEventHandler;
|
|
}
|
|
}
|
|
|
|
EventTarget* nsGlobalWindowOuter::GetTargetForEventTargetChain() {
|
|
return GetCurrentInnerWindowInternal();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
|
|
MOZ_CRASH("The outer window should not be part of an event path");
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::ShouldPromptToBlockDialogs() {
|
|
nsGlobalWindowOuter* topWindowOuter = GetScriptableTopInternal();
|
|
if (!topWindowOuter) {
|
|
NS_ASSERTION(!mDocShell,
|
|
"ShouldPromptToBlockDialogs() called without a top window?");
|
|
return true;
|
|
}
|
|
|
|
nsGlobalWindowInner* topWindow =
|
|
topWindowOuter->GetCurrentInnerWindowInternal();
|
|
if (!topWindow) {
|
|
return true;
|
|
}
|
|
|
|
return topWindow->DialogsAreBeingAbused();
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::AreDialogsEnabled() {
|
|
nsGlobalWindowOuter* topWindowOuter = GetScriptableTopInternal();
|
|
if (!topWindowOuter) {
|
|
NS_ERROR("AreDialogsEnabled() called without a top window?");
|
|
return false;
|
|
}
|
|
|
|
// TODO: Warn if no top window?
|
|
nsGlobalWindowInner* topWindow =
|
|
topWindowOuter->GetCurrentInnerWindowInternal();
|
|
if (!topWindow) {
|
|
return false;
|
|
}
|
|
|
|
// Dialogs are blocked if the content viewer is hidden
|
|
if (mDocShell) {
|
|
nsCOMPtr<nsIContentViewer> cv;
|
|
mDocShell->GetContentViewer(getter_AddRefs(cv));
|
|
|
|
bool isHidden;
|
|
cv->GetIsHidden(&isHidden);
|
|
if (isHidden) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Dialogs are also blocked if the document is sandboxed with SANDBOXED_MODALS
|
|
// (or if we have no document, of course). Which document? Who knows; the
|
|
// spec is daft. See <https://github.com/whatwg/html/issues/1206>. For now
|
|
// just go ahead and check mDoc, since in everything except edge cases in
|
|
// which a frame is allow-same-origin but not allow-scripts and is being poked
|
|
// at by some other window this should be the right thing anyway.
|
|
if (!mDoc || (mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
|
|
return false;
|
|
}
|
|
|
|
return topWindow->mAreDialogsEnabled;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::ConfirmDialogIfNeeded() {
|
|
NS_ENSURE_TRUE(mDocShell, false);
|
|
nsCOMPtr<nsIPromptService> 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.
|
|
AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
|
|
|
|
bool disableDialog = false;
|
|
nsAutoString 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 nsGlobalWindowOuter::DisableDialogs() {
|
|
nsGlobalWindowOuter* topWindowOuter = GetScriptableTopInternal();
|
|
if (!topWindowOuter) {
|
|
NS_ERROR("DisableDialogs() called without a top window?");
|
|
return;
|
|
}
|
|
|
|
nsGlobalWindowInner* topWindow =
|
|
topWindowOuter->GetCurrentInnerWindowInternal();
|
|
// TODO: Warn if no top window?
|
|
if (topWindow) {
|
|
topWindow->mAreDialogsEnabled = false;
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::EnableDialogs() {
|
|
nsGlobalWindowOuter* topWindowOuter = GetScriptableTopInternal();
|
|
if (!topWindowOuter) {
|
|
NS_ERROR("EnableDialogs() called without a top window?");
|
|
return;
|
|
}
|
|
|
|
// TODO: Warn if no top window?
|
|
nsGlobalWindowInner* topWindow =
|
|
topWindowOuter->GetCurrentInnerWindowInternal();
|
|
if (topWindow) {
|
|
topWindow->mAreDialogsEnabled = true;
|
|
}
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::PostHandleEvent(EventChainPostVisitor& aVisitor) {
|
|
MOZ_CRASH("The outer window should not be part of an event path");
|
|
}
|
|
|
|
void nsGlobalWindowOuter::PoisonOuterWindowProxy(JSObject* aObject) {
|
|
if (aObject == GetWrapperMaybeDead()) {
|
|
PoisonWrapper();
|
|
}
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::SetArguments(nsIArray* aArguments) {
|
|
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.
|
|
nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();
|
|
|
|
mArguments = aArguments;
|
|
rv = currentInner->DefineArgumentsProperty(aArguments);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsIScriptObjectPrincipal
|
|
//*****************************************************************************
|
|
|
|
nsIPrincipal* nsGlobalWindowOuter::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 <frame src="javascript:xxx">, in
|
|
// that case the global window is used in JS before we've loaded
|
|
// a document into the window.
|
|
|
|
nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
|
|
do_QueryInterface(GetParentInternal());
|
|
|
|
if (objPrincipal) {
|
|
return objPrincipal->GetPrincipal();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
nsIPrincipal* nsGlobalWindowOuter::GetEffectiveStoragePrincipal() {
|
|
if (mDoc) {
|
|
// If we have a document, get the principal from the document
|
|
return mDoc->EffectiveStoragePrincipal();
|
|
}
|
|
|
|
if (mDocumentStoragePrincipal) {
|
|
return mDocumentStoragePrincipal;
|
|
}
|
|
|
|
// If we don't have a storage principal and we don't have a document we ask
|
|
// the parent window for the storage principal.
|
|
|
|
nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
|
|
do_QueryInterface(GetParentInternal());
|
|
|
|
if (objPrincipal) {
|
|
return objPrincipal->GetEffectiveStoragePrincipal();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsIDOMWindow
|
|
//*****************************************************************************
|
|
|
|
void nsPIDOMWindowOuter::SetInitialKeyboardIndicators(
|
|
UIStateChangeType aShowFocusRings) {
|
|
MOZ_ASSERT(!GetCurrentInnerWindow());
|
|
|
|
nsPIDOMWindowOuter* piWin = GetPrivateRoot();
|
|
if (!piWin) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(piWin == this);
|
|
|
|
// only change the flags that have been modified
|
|
nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler);
|
|
if (!windowRoot) {
|
|
return;
|
|
}
|
|
|
|
if (aShowFocusRings != UIStateChangeType_NoChange) {
|
|
windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set);
|
|
}
|
|
|
|
nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(this, aShowFocusRings);
|
|
}
|
|
|
|
Element* nsPIDOMWindowOuter::GetFrameElementInternal() const {
|
|
return mFrameElement;
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement) {
|
|
mFrameElement = aFrameElement;
|
|
}
|
|
|
|
Navigator* nsGlobalWindowOuter::GetNavigator() {
|
|
FORWARD_TO_INNER(Navigator, (), nullptr);
|
|
}
|
|
|
|
nsScreen* nsGlobalWindowOuter::GetScreen() {
|
|
FORWARD_TO_INNER(GetScreen, (IgnoreErrors()), nullptr);
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::MaybeActiveMediaComponents() {
|
|
if (mMediaSuspend != nsISuspendedTypes::SUSPENDED_BLOCK) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
|
|
("nsPIDOMWindowOuter, MaybeActiveMediaComponents, "
|
|
"resume the window from blocked, this = %p\n",
|
|
this));
|
|
|
|
SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
|
|
}
|
|
|
|
SuspendTypes nsPIDOMWindowOuter::GetMediaSuspend() const {
|
|
return mMediaSuspend;
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend) {
|
|
if (!IsDisposableSuspend(aSuspend)) {
|
|
MaybeNotifyMediaResumedFromBlock(aSuspend);
|
|
mMediaSuspend = aSuspend;
|
|
}
|
|
|
|
RefreshMediaElementsSuspend(aSuspend);
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::MaybeNotifyMediaResumedFromBlock(
|
|
SuspendTypes aSuspend) {
|
|
if (mMediaSuspend == nsISuspendedTypes::SUSPENDED_BLOCK &&
|
|
aSuspend == nsISuspendedTypes::NONE_SUSPENDED) {
|
|
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
|
|
if (service) {
|
|
service->NotifyMediaResumedFromBlock(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool nsPIDOMWindowOuter::GetAudioMuted() const { return mAudioMuted; }
|
|
|
|
void nsPIDOMWindowOuter::SetAudioMuted(bool aMuted) {
|
|
if (mAudioMuted == aMuted) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
|
|
("nsPIDOMWindowOuter %p, SetAudioMuted=%s", this,
|
|
aMuted ? "muted" : "unmuted"));
|
|
mAudioMuted = aMuted;
|
|
RefreshMediaElementsVolume();
|
|
}
|
|
|
|
float nsPIDOMWindowOuter::GetAudioVolume() const { return mAudioVolume; }
|
|
|
|
nsresult nsPIDOMWindowOuter::SetAudioVolume(float aVolume) {
|
|
if (aVolume < 0.0) {
|
|
return NS_ERROR_DOM_INDEX_SIZE_ERR;
|
|
}
|
|
|
|
if (mAudioVolume == aVolume) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mAudioVolume = aVolume;
|
|
RefreshMediaElementsVolume();
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::RefreshMediaElementsVolume() {
|
|
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
|
|
if (service) {
|
|
service->RefreshAgentsVolume(this);
|
|
}
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::RefreshMediaElementsSuspend(SuspendTypes aSuspend) {
|
|
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
|
|
if (service) {
|
|
service->RefreshAgentsSuspend(this, aSuspend);
|
|
}
|
|
}
|
|
|
|
bool nsPIDOMWindowOuter::IsDisposableSuspend(SuspendTypes aSuspend) const {
|
|
return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
|
|
aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled) {
|
|
// Devtools should only be setting this on the top level window. Its
|
|
// ok if devtools clears the flag on clean up of nested windows, though.
|
|
// It will have no affect.
|
|
#ifdef DEBUG
|
|
nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
|
|
MOZ_ASSERT_IF(aEnabled, this == topWindow);
|
|
#endif
|
|
mServiceWorkersTestingEnabled = aEnabled;
|
|
}
|
|
|
|
bool nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled() {
|
|
// Automatically get this setting from the top level window so that nested
|
|
// iframes get the correct devtools setting.
|
|
nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
|
|
if (!topWindow) {
|
|
return false;
|
|
}
|
|
return topWindow->mServiceWorkersTestingEnabled;
|
|
}
|
|
|
|
Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetParentOuter() {
|
|
BrowsingContext* bc = GetBrowsingContext();
|
|
return bc ? bc->GetParent(IgnoreErrors()) : nullptr;
|
|
}
|
|
|
|
/**
|
|
* GetScriptableParent is called when script reads window.parent.
|
|
*
|
|
* In contrast to GetRealParent, GetScriptableParent respects <iframe
|
|
* mozbrowser> boundaries, so if |this| is contained by an <iframe
|
|
* mozbrowser>, we will return |this| as its own parent.
|
|
*/
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetScriptableParent() {
|
|
if (!mDocShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (mDocShell->GetIsMozBrowser()) {
|
|
return this;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
|
|
return parent;
|
|
}
|
|
|
|
/**
|
|
* Behavies identically to GetScriptableParent extept that it returns null
|
|
* if GetScriptableParent would return this window.
|
|
*/
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetScriptableParentOrNull() {
|
|
nsPIDOMWindowOuter* parent = GetScriptableParent();
|
|
return (nsGlobalWindowOuter::Cast(parent) == this) ? nullptr : parent;
|
|
}
|
|
|
|
/**
|
|
* nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around
|
|
* GetRealParent.
|
|
*/
|
|
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetParent() {
|
|
if (!mDocShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> parent;
|
|
mDocShell->GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent));
|
|
|
|
if (parent) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow();
|
|
return win.forget();
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> win(this);
|
|
return win.forget();
|
|
}
|
|
|
|
static nsresult GetTopImpl(nsGlobalWindowOuter* aWin, nsIURI* aURIBeingLoaded,
|
|
nsPIDOMWindowOuter** aTop, bool aScriptable,
|
|
bool aExcludingExtensionAccessibleContentFrames) {
|
|
*aTop = nullptr;
|
|
|
|
MOZ_ASSERT_IF(aExcludingExtensionAccessibleContentFrames, !aScriptable);
|
|
|
|
// Walk up the parent chain.
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> prevParent = aWin;
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent = aWin;
|
|
do {
|
|
if (!parent) {
|
|
break;
|
|
}
|
|
|
|
prevParent = parent;
|
|
|
|
if (aScriptable) {
|
|
parent = parent->GetScriptableParent();
|
|
} else {
|
|
parent = parent->GetParent();
|
|
}
|
|
|
|
if (aExcludingExtensionAccessibleContentFrames) {
|
|
if (auto* p = nsGlobalWindowOuter::Cast(parent)) {
|
|
nsGlobalWindowInner* currentInner = p->GetCurrentInnerWindowInternal();
|
|
nsIURI* uri = prevParent->GetDocumentURI();
|
|
if (!uri) {
|
|
// If our parent doesn't have a URI yet, we have a document that is in
|
|
// the process of being loaded. In that case, our caller is
|
|
// responsible for passing in the URI for the document that is being
|
|
// loaded, so we fall back to using that URI here.
|
|
uri = aURIBeingLoaded;
|
|
}
|
|
|
|
if (currentInner && uri) {
|
|
// If we find an inner window, we better find the uri for the current
|
|
// window we're looking at. If we can't find it directly, it is the
|
|
// responsibility of our caller to provide it to us.
|
|
MOZ_DIAGNOSTIC_ASSERT(uri);
|
|
|
|
// If the new parent has permission to load the current page, we're
|
|
// at a moz-extension:// frame which has a host permission that allows
|
|
// it to load the document that we've loaded. In that case, stop at
|
|
// this frame and consider it the top-level frame.
|
|
//
|
|
// Note that it's possible for the set of URIs accepted by
|
|
// AddonAllowsLoad() to change at runtime, but we don't need to cache
|
|
// the result of this check, since the important consumer of this code
|
|
// (which is nsIHttpChannelInternal.topWindowURI) already caches the
|
|
// result after computing it the first time.
|
|
if (BasePrincipal::Cast(p->GetPrincipal())
|
|
->AddonAllowsLoad(uri, true)) {
|
|
parent = prevParent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (parent != prevParent);
|
|
|
|
if (parent) {
|
|
parent.swap(*aTop);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* GetScriptableTop is called when script reads window.top.
|
|
*
|
|
* In contrast to GetRealTop, GetScriptableTop respects <iframe mozbrowser>
|
|
* boundaries. If we encounter a window owned by an <iframe mozbrowser> while
|
|
* walking up the window hierarchy, we'll stop and return that window.
|
|
*/
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetScriptableTop() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> window;
|
|
GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
|
|
/* aScriptable = */ true,
|
|
/* aExcludingExtensionAccessibleContentFrames = */ false);
|
|
return window.get();
|
|
}
|
|
|
|
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetTop() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> window;
|
|
GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
|
|
/* aScriptable = */ false,
|
|
/* aExcludingExtensionAccessibleContentFrames = */ false);
|
|
return window.forget();
|
|
}
|
|
|
|
already_AddRefed<nsPIDOMWindowOuter>
|
|
nsGlobalWindowOuter::GetTopExcludingExtensionAccessibleContentFrames(
|
|
nsIURI* aURIBeingLoaded) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> window;
|
|
GetTopImpl(this, aURIBeingLoaded, getter_AddRefs(window),
|
|
/* aScriptable = */ false,
|
|
/* aExcludingExtensionAccessibleContentFrames = */ true);
|
|
return window.forget();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::GetContentOuter(JSContext* aCx,
|
|
JS::MutableHandle<JSObject*> aRetval,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> content =
|
|
GetContentInternal(aError, aCallerType);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
if (content) {
|
|
JS::Rooted<JS::Value> val(aCx);
|
|
aError = nsContentUtils::WrapNative(aCx, content, &val);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
aRetval.set(&val.toObject());
|
|
return;
|
|
}
|
|
|
|
aRetval.set(nullptr);
|
|
}
|
|
|
|
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetContentInternal(
|
|
ErrorResult& aError, CallerType aCallerType) {
|
|
// First check for a named frame named "content"
|
|
RefPtr<BrowsingContext> bc = GetChildWindow(NS_LITERAL_STRING("content"));
|
|
if (bc) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> content(bc->GetDOMWindow());
|
|
return content.forget();
|
|
}
|
|
|
|
// If we're contained in <iframe mozbrowser>, then GetContent is the same as
|
|
// window.top.
|
|
if (mDocShell && mDocShell->GetIsInMozBrowser()) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> domWindow(GetScriptableTop());
|
|
return domWindow.forget();
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> primaryContent;
|
|
if (aCallerType != CallerType::System) {
|
|
if (mDoc) {
|
|
mDoc->WarnOnceAbout(Document::eWindowContentUntrusted);
|
|
}
|
|
// If we're called by non-chrome code, make sure we don't return
|
|
// the primary content window if the calling tab is hidden. In
|
|
// such a case we return the same-type root in the hidden tab,
|
|
// which is "good enough", for now.
|
|
nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(mDocShell));
|
|
|
|
if (baseWin) {
|
|
bool visible = false;
|
|
baseWin->GetVisibility(&visible);
|
|
|
|
if (!visible) {
|
|
mDocShell->GetSameTypeRootTreeItem(getter_AddRefs(primaryContent));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!primaryContent) {
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
|
|
if (!treeOwner) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent));
|
|
}
|
|
|
|
if (!primaryContent) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> domWindow = primaryContent->GetWindow();
|
|
return domWindow.forget();
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::GetPrompter(nsIPrompt** aPrompt) {
|
|
if (!mDocShell) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mDocShell));
|
|
NS_ENSURE_TRUE(prompter, NS_ERROR_NO_INTERFACE);
|
|
|
|
prompter.forget(aPrompt);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::GetClosedOuter() {
|
|
// If someone called close(), or if we don't have a docshell, we're closed.
|
|
return mIsClosed || !mDocShell;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::Closed() { return GetClosedOuter(); }
|
|
|
|
Nullable<WindowProxyHolder> nsGlobalWindowOuter::IndexedGetterOuter(
|
|
uint32_t aIndex) {
|
|
BrowsingContext* bc = GetBrowsingContext();
|
|
NS_ENSURE_TRUE(bc, nullptr);
|
|
|
|
const BrowsingContext::Children& children = bc->GetChildren();
|
|
if (aIndex < children.Length()) {
|
|
return WindowProxyHolder(children[aIndex]);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
|
|
if (!mControllers) {
|
|
mControllers = new nsXULControllers();
|
|
if (!mControllers) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
// Add in the default controller
|
|
RefPtr<nsBaseCommandController> commandController =
|
|
nsBaseCommandController::CreateWindowController();
|
|
if (!commandController) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
mControllers->InsertControllerAt(0, commandController);
|
|
commandController->SetCommandContext(static_cast<nsIDOMWindow*>(this));
|
|
}
|
|
|
|
return mControllers;
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::GetControllers(nsIControllers** aResult) {
|
|
FORWARD_TO_INNER(GetControllers, (aResult), NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetSanitizedOpener(
|
|
nsPIDOMWindowOuter* aOpener) {
|
|
if (!aOpener) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsGlobalWindowOuter* win = nsGlobalWindowOuter::Cast(aOpener);
|
|
|
|
// First, ensure that we're not handing back a chrome window to content:
|
|
if (win->IsChromeWindow()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We don't want to reveal the opener if the opener is a mail window,
|
|
// because opener can be used to spoof the contents of a message (bug 105050).
|
|
// So, we look in the opener's root docshell to see if it's a mail window.
|
|
nsCOMPtr<nsIDocShell> openerDocShell = aOpener->GetDocShell();
|
|
|
|
if (openerDocShell) {
|
|
nsCOMPtr<nsIDocShellTreeItem> openerRootItem;
|
|
openerDocShell->GetRootTreeItem(getter_AddRefs(openerRootItem));
|
|
nsCOMPtr<nsIDocShell> openerRootDocShell(do_QueryInterface(openerRootItem));
|
|
if (openerRootDocShell) {
|
|
nsIDocShell::AppType appType = openerRootDocShell->GetAppType();
|
|
if (appType != nsIDocShell::APP_TYPE_MAIL) {
|
|
return aOpener;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOpenerWindowOuter() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryReferent(mOpener);
|
|
|
|
if (!opener) {
|
|
return nullptr;
|
|
}
|
|
|
|
// First, check if we were called from a privileged chrome script
|
|
if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
|
|
// Catch the case where we're chrome but the opener is not...
|
|
if (GetPrincipal() == nsContentUtils::GetSystemPrincipal() &&
|
|
nsGlobalWindowOuter::Cast(opener)->GetPrincipal() !=
|
|
nsContentUtils::GetSystemPrincipal()) {
|
|
return nullptr;
|
|
}
|
|
return opener;
|
|
}
|
|
|
|
return GetSanitizedOpener(opener);
|
|
}
|
|
|
|
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetOpener() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindowOuter();
|
|
return opener.forget();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::GetStatusOuter(nsAString& aStatus) {
|
|
aStatus = mStatus;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetStatusOuter(const nsAString& aStatus) {
|
|
mStatus = aStatus;
|
|
|
|
// We don't support displaying window.status in the UI, so there's nothing
|
|
// left to do here.
|
|
}
|
|
|
|
void nsGlobalWindowOuter::GetNameOuter(nsAString& aName) {
|
|
if (mDocShell) {
|
|
mDocShell->GetName(aName);
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetNameOuter(const nsAString& aName,
|
|
mozilla::ErrorResult& aError) {
|
|
if (mDocShell) {
|
|
aError = mDocShell->SetName(aName);
|
|
}
|
|
}
|
|
|
|
// Helper functions used by many methods below.
|
|
int32_t nsGlobalWindowOuter::DevToCSSIntPixels(int32_t px) {
|
|
if (!mDocShell) return px; // assume 1:1
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (!presContext) return px;
|
|
|
|
return presContext->DevPixelsToIntCSSPixels(px);
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::CSSToDevIntPixels(int32_t px) {
|
|
if (!mDocShell) return px; // assume 1:1
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (!presContext) return px;
|
|
|
|
return presContext->CSSPixelsToDevPixels(px);
|
|
}
|
|
|
|
nsIntSize nsGlobalWindowOuter::DevToCSSIntPixels(nsIntSize px) {
|
|
if (!mDocShell) return px; // assume 1:1
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (!presContext) return px;
|
|
|
|
return nsIntSize(presContext->DevPixelsToIntCSSPixels(px.width),
|
|
presContext->DevPixelsToIntCSSPixels(px.height));
|
|
}
|
|
|
|
nsIntSize nsGlobalWindowOuter::CSSToDevIntPixels(nsIntSize px) {
|
|
if (!mDocShell) return px; // assume 1:1
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (!presContext) return px;
|
|
|
|
return nsIntSize(presContext->CSSPixelsToDevPixels(px.width),
|
|
presContext->CSSPixelsToDevPixels(px.height));
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::GetInnerSize(CSSIntSize& aSize) {
|
|
EnsureSizeAndPositionUpToDate();
|
|
|
|
NS_ENSURE_STATE(mDocShell);
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
PresShell* presShell = mDocShell->GetPresShell();
|
|
|
|
if (!presContext || !presShell) {
|
|
aSize = CSSIntSize(0, 0);
|
|
return NS_OK;
|
|
}
|
|
|
|
// If the visual viewport has been overriden, return that.
|
|
if (presShell->IsVisualViewportSizeSet()) {
|
|
aSize = CSSIntRect::FromAppUnitsRounded(presShell->GetVisualViewportSize());
|
|
return NS_OK;
|
|
}
|
|
|
|
// Whether or not the css viewport has been overridden, we can get the
|
|
// correct value by looking at the visible area of the presContext.
|
|
RefPtr<nsViewManager> viewManager = presShell->GetViewManager();
|
|
if (viewManager) {
|
|
viewManager->FlushDelayedResize(false);
|
|
}
|
|
|
|
aSize = CSSIntRect::FromAppUnitsRounded(presContext->GetVisibleArea().Size());
|
|
return NS_OK;
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetInnerWidthOuter(ErrorResult& aError) {
|
|
CSSIntSize size;
|
|
aError = GetInnerSize(size);
|
|
return size.width;
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::GetInnerWidth(int32_t* aInnerWidth) {
|
|
FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetInnerWidthOuter(int32_t aInnerWidth,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
if (!mDocShell) {
|
|
aError.Throw(NS_ERROR_UNEXPECTED);
|
|
return;
|
|
}
|
|
|
|
CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerType);
|
|
RefPtr<PresShell> presShell = mDocShell->GetPresShell();
|
|
|
|
// Setting inner width should set the visual viewport. Most of the
|
|
// time, this is the same as the CSS viewport, and when we set one,
|
|
// we implicitly set both of them. But if
|
|
// presShell->IsVisualViewportSizeSet() returns true, that means
|
|
// that the two diverge. In that case we only set the visual viewport.
|
|
// This mirrors the logic in ::GetInnerSize() and ensures that JS
|
|
// behaves sanely when setting and then getting the innerWidth.
|
|
if (presShell && presShell->IsVisualViewportSizeSet()) {
|
|
CSSSize viewportSize =
|
|
CSSRect::FromAppUnits(presShell->GetVisualViewportSize());
|
|
viewportSize.width = aInnerWidth;
|
|
nsLayoutUtils::SetVisualViewportSize(presShell, viewportSize);
|
|
return;
|
|
}
|
|
|
|
// We're going to set both viewports. If the css viewport has been
|
|
// overridden, change the css viewport override. The visual viewport
|
|
// will adopt this value via the logic in ::GetInnerSize().
|
|
if (presShell && presShell->GetIsViewportOverridden()) {
|
|
nscoord height = 0;
|
|
|
|
RefPtr<nsPresContext> presContext;
|
|
presContext = presShell->GetPresContext();
|
|
|
|
nsRect shellArea = presContext->GetVisibleArea();
|
|
height = shellArea.Height();
|
|
SetCSSViewportWidthAndHeight(
|
|
nsPresContext::CSSPixelsToAppUnits(aInnerWidth), height);
|
|
return;
|
|
}
|
|
|
|
// Nothing has been overriden, so change the docshell itself, which will
|
|
// affect both viewports.
|
|
int32_t height = 0;
|
|
int32_t unused = 0;
|
|
|
|
nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
|
|
docShellAsWin->GetSize(&unused, &height);
|
|
aError = SetDocShellWidthAndHeight(CSSToDevIntPixels(aInnerWidth), height);
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetInnerHeightOuter(ErrorResult& aError) {
|
|
CSSIntSize size;
|
|
aError = GetInnerSize(size);
|
|
return size.height;
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::GetInnerHeight(int32_t* aInnerHeight) {
|
|
FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetInnerHeightOuter(int32_t aInnerHeight,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
if (!mDocShell) {
|
|
aError.Throw(NS_ERROR_UNEXPECTED);
|
|
return;
|
|
}
|
|
|
|
CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerType);
|
|
RefPtr<PresShell> presShell = mDocShell->GetPresShell();
|
|
|
|
// Setting inner height should set the visual viewport. Most of the
|
|
// time, this is the same as the CSS viewport, and when we set one,
|
|
// we implicitly set both of them. But if
|
|
// presShell->IsVisualViewportSizeSet() returns true, that means
|
|
// that the two diverge. In that case we only set the visual viewport.
|
|
// This mirrors the logic in ::GetInnerSize() and ensures that JS
|
|
// behaves sanely when setting and then getting the innerHeight.
|
|
if (presShell && presShell->IsVisualViewportSizeSet()) {
|
|
CSSSize viewportSize =
|
|
CSSRect::FromAppUnits(presShell->GetVisualViewportSize());
|
|
viewportSize.height = aInnerHeight;
|
|
nsLayoutUtils::SetVisualViewportSize(presShell, viewportSize);
|
|
return;
|
|
}
|
|
|
|
// We're going to set both viewports. If the css viewport has been
|
|
// overridden, change the css viewport override. The visual viewport
|
|
// will adopt this value via the logic in ::GetInnerSize().
|
|
if (presShell && presShell->GetIsViewportOverridden()) {
|
|
nscoord width = 0;
|
|
|
|
RefPtr<nsPresContext> presContext;
|
|
presContext = presShell->GetPresContext();
|
|
|
|
nsRect shellArea = presContext->GetVisibleArea();
|
|
width = shellArea.Width();
|
|
SetCSSViewportWidthAndHeight(
|
|
width, nsPresContext::CSSPixelsToAppUnits(aInnerHeight));
|
|
return;
|
|
}
|
|
|
|
// Nothing has been overriden, so change the docshell itself, which will
|
|
// affect both viewports.
|
|
int32_t height = 0;
|
|
int32_t width = 0;
|
|
|
|
nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
|
|
docShellAsWin->GetSize(&width, &height);
|
|
aError = SetDocShellWidthAndHeight(width, CSSToDevIntPixels(aInnerHeight));
|
|
}
|
|
|
|
nsIntSize nsGlobalWindowOuter::GetOuterSize(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
|
|
CSSIntSize size;
|
|
aError = GetInnerSize(size);
|
|
return nsIntSize(size.width, size.height);
|
|
}
|
|
|
|
if (mDoc && mDoc->InRDMPane()) {
|
|
CSSIntSize size;
|
|
aError = GetInnerSize(size);
|
|
|
|
// Obtain the current zoom of the presentation shell. The zoom value will
|
|
// be used to scale the size of the visual viewport to the device browser's
|
|
// outer size values. Once RDM no longer relies on the having the page
|
|
// content being embedded in a <iframe mozbrowser>, we can do away with
|
|
// this approach and retrieve the size of the frame containing the browser
|
|
// content.
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
|
|
if (presContext) {
|
|
float zoom = presContext->GetDeviceFullZoom();
|
|
int32_t width = std::round(size.width * zoom);
|
|
int32_t height = std::round(size.height * zoom);
|
|
return nsIntSize(width, height);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return nsIntSize(0, 0);
|
|
}
|
|
|
|
nsIntSize sizeDevPixels;
|
|
aError = treeOwnerAsWin->GetSize(&sizeDevPixels.width, &sizeDevPixels.height);
|
|
if (aError.Failed()) {
|
|
return nsIntSize();
|
|
}
|
|
|
|
return DevToCSSIntPixels(sizeDevPixels);
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetOuterWidthOuter(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
return GetOuterSize(aCallerType, aError).width;
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetOuterHeightOuter(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
return GetOuterSize(aCallerType, aError).height;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
|
|
aIsWidth ? nullptr : &aLengthCSSPixels,
|
|
aCallerType);
|
|
|
|
int32_t width, height;
|
|
aError = treeOwnerAsWin->GetSize(&width, &height);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
int32_t lengthDevPixels = CSSToDevIntPixels(aLengthCSSPixels);
|
|
if (aIsWidth) {
|
|
width = lengthDevPixels;
|
|
} else {
|
|
height = lengthDevPixels;
|
|
}
|
|
aError = treeOwnerAsWin->SetSize(width, height, true);
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetOuterWidthOuter(int32_t aOuterWidth,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
SetOuterSize(aOuterWidth, true, aCallerType, aError);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetOuterHeightOuter(int32_t aOuterHeight,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
SetOuterSize(aOuterHeight, false, aCallerType, aError);
|
|
}
|
|
|
|
CSSIntPoint nsGlobalWindowOuter::GetScreenXY(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
// When resisting fingerprinting, always return (0,0)
|
|
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
|
|
return CSSIntPoint(0, 0);
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return CSSIntPoint(0, 0);
|
|
}
|
|
|
|
int32_t x = 0, y = 0;
|
|
aError = treeOwnerAsWin->GetPosition(&x, &y); // LayoutDevice px values
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (!presContext) {
|
|
return CSSIntPoint(x, y);
|
|
}
|
|
|
|
// Find the global desktop coordinate of the top-left of the screen.
|
|
// We'll use this as a "fake origin" when converting to CSS px units,
|
|
// to avoid overlapping coordinates in cases such as a hi-dpi screen
|
|
// placed to the right of a lo-dpi screen on Windows. (Instead, there
|
|
// may be "gaps" in the resulting CSS px coordinates in some cases.)
|
|
nsDeviceContext* dc = presContext->DeviceContext();
|
|
nsRect screenRect;
|
|
dc->GetRect(screenRect);
|
|
LayoutDeviceRect screenRectDev =
|
|
LayoutDevicePixel::FromAppUnits(screenRect, dc->AppUnitsPerDevPixel());
|
|
|
|
DesktopToLayoutDeviceScale scale = dc->GetDesktopToDeviceScale();
|
|
DesktopRect screenRectDesk = screenRectDev / scale;
|
|
|
|
CSSPoint cssPt = LayoutDevicePoint(x - screenRectDev.x, y - screenRectDev.y) /
|
|
presContext->CSSToDevPixelScale();
|
|
cssPt.x += screenRectDesk.x;
|
|
cssPt.y += screenRectDesk.y;
|
|
|
|
return CSSIntPoint(NSToIntRound(cssPt.x), NSToIntRound(cssPt.y));
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetScreenXOuter(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
return GetScreenXY(aCallerType, aError).x;
|
|
}
|
|
|
|
nsRect nsGlobalWindowOuter::GetInnerScreenRect() {
|
|
if (!mDocShell) {
|
|
return nsRect();
|
|
}
|
|
|
|
EnsureSizeAndPositionUpToDate();
|
|
|
|
if (!mDocShell) {
|
|
return nsRect();
|
|
}
|
|
|
|
PresShell* presShell = mDocShell->GetPresShell();
|
|
if (!presShell) {
|
|
return nsRect();
|
|
}
|
|
nsIFrame* rootFrame = presShell->GetRootFrame();
|
|
if (!rootFrame) {
|
|
return nsRect();
|
|
}
|
|
|
|
return rootFrame->GetScreenRectInAppUnits();
|
|
}
|
|
|
|
float nsGlobalWindowOuter::GetMozInnerScreenXOuter(CallerType aCallerType) {
|
|
// When resisting fingerprinting, always return 0.
|
|
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
|
|
return 0.0;
|
|
}
|
|
|
|
nsRect r = GetInnerScreenRect();
|
|
return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
|
|
}
|
|
|
|
float nsGlobalWindowOuter::GetMozInnerScreenYOuter(CallerType aCallerType) {
|
|
// Return 0 to prevent fingerprinting.
|
|
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
|
|
return 0.0;
|
|
}
|
|
|
|
nsRect r = GetInnerScreenRect();
|
|
return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
|
|
}
|
|
|
|
double nsGlobalWindowOuter::GetDevicePixelRatioOuter(CallerType aCallerType) {
|
|
if (!mDocShell) {
|
|
return 1.0;
|
|
}
|
|
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (!presContext) {
|
|
return 1.0;
|
|
}
|
|
|
|
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
|
|
return 1.0;
|
|
}
|
|
|
|
float overrideDPPX = presContext->GetOverrideDPPX();
|
|
|
|
if (overrideDPPX > 0) {
|
|
return overrideDPPX;
|
|
}
|
|
|
|
return double(AppUnitsPerCSSPixel()) /
|
|
double(presContext->AppUnitsPerDevPixel());
|
|
}
|
|
|
|
float nsPIDOMWindowOuter::GetDevicePixelRatio(CallerType aCallerType) {
|
|
return nsGlobalWindowOuter::Cast(this)->GetDevicePixelRatioOuter(aCallerType);
|
|
}
|
|
|
|
uint64_t nsGlobalWindowOuter::GetMozPaintCountOuter() {
|
|
if (!mDocShell) {
|
|
return 0;
|
|
}
|
|
|
|
PresShell* presShell = mDocShell->GetPresShell();
|
|
return presShell ? presShell->GetPaintCount() : 0;
|
|
}
|
|
|
|
already_AddRefed<MediaQueryList> nsGlobalWindowOuter::MatchMediaOuter(
|
|
const nsAString& aMediaQueryList, CallerType aCallerType) {
|
|
if (!mDoc) {
|
|
return nullptr;
|
|
}
|
|
|
|
return mDoc->MatchMedia(aMediaQueryList, aCallerType);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetScreenXOuter(int32_t aScreenX,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
int32_t x, y;
|
|
aError = treeOwnerAsWin->GetPosition(&x, &y);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
CheckSecurityLeftAndTop(&aScreenX, nullptr, aCallerType);
|
|
x = CSSToDevIntPixels(aScreenX);
|
|
|
|
aError = treeOwnerAsWin->SetPosition(x, y);
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetScreenYOuter(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
return GetScreenXY(aCallerType, aError).y;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetScreenYOuter(int32_t aScreenY,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
int32_t x, y;
|
|
aError = treeOwnerAsWin->GetPosition(&x, &y);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
CheckSecurityLeftAndTop(nullptr, &aScreenY, aCallerType);
|
|
y = CSSToDevIntPixels(aScreenY);
|
|
|
|
aError = treeOwnerAsWin->SetPosition(x, y);
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
// NOTE: Arguments to this function should have values scaled to
|
|
// CSS pixels, not device pixels.
|
|
void nsGlobalWindowOuter::CheckSecurityWidthAndHeight(int32_t* aWidth,
|
|
int32_t* aHeight,
|
|
CallerType aCallerType) {
|
|
#ifdef MOZ_XUL
|
|
if (aCallerType != CallerType::System) {
|
|
// if attempting to resize the window, hide any open popups
|
|
nsContentUtils::HidePopupsInDocument(mDoc);
|
|
}
|
|
#endif
|
|
|
|
// This one is easy. Just ensure the variable is greater than 100;
|
|
if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) {
|
|
// Check security state for use in determing window dimensions
|
|
|
|
if (aCallerType != CallerType::System) {
|
|
// sec check failed
|
|
if (aWidth && *aWidth < 100) {
|
|
*aWidth = 100;
|
|
}
|
|
if (aHeight && *aHeight < 100) {
|
|
*aHeight = 100;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: Arguments to this function should have values in device pixels
|
|
nsresult nsGlobalWindowOuter::SetDocShellWidthAndHeight(int32_t aInnerWidth,
|
|
int32_t aInnerHeight) {
|
|
NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = mDocShell;
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
|
docShell->GetTreeOwner(getter_AddRefs(treeOwner));
|
|
NS_ENSURE_TRUE(treeOwner, NS_ERROR_FAILURE);
|
|
|
|
NS_ENSURE_SUCCESS(treeOwner->SizeShellTo(docShell, aInnerWidth, aInnerHeight),
|
|
NS_ERROR_FAILURE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// NOTE: Arguments to this function should have values in app units
|
|
void nsGlobalWindowOuter::SetCSSViewportWidthAndHeight(nscoord aInnerWidth,
|
|
nscoord aInnerHeight) {
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
|
|
nsRect shellArea = presContext->GetVisibleArea();
|
|
shellArea.SetHeight(aInnerHeight);
|
|
shellArea.SetWidth(aInnerWidth);
|
|
|
|
presContext->SetVisibleArea(shellArea);
|
|
}
|
|
|
|
// NOTE: Arguments to this function should have values scaled to
|
|
// CSS pixels, not device pixels.
|
|
void nsGlobalWindowOuter::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop,
|
|
CallerType aCallerType) {
|
|
// This one is harder. We have to get the screen size and window dimensions.
|
|
|
|
// Check security state for use in determing window dimensions
|
|
|
|
if (aCallerType != CallerType::System) {
|
|
#ifdef MOZ_XUL
|
|
// if attempting to move the window, hide any open popups
|
|
nsContentUtils::HidePopupsInDocument(mDoc);
|
|
#endif
|
|
|
|
if (nsGlobalWindowOuter* rootWindow =
|
|
nsGlobalWindowOuter::Cast(GetPrivateRoot())) {
|
|
rootWindow->FlushPendingNotifications(FlushType::Layout);
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwner = GetTreeOwnerWindow();
|
|
|
|
RefPtr<nsScreen> screen = GetScreen();
|
|
|
|
if (treeOwner && screen) {
|
|
int32_t winLeft, winTop, winWidth, winHeight;
|
|
|
|
// Get the window size
|
|
treeOwner->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);
|
|
|
|
// convert those values to CSS pixels
|
|
// XXX four separate retrievals of the prescontext
|
|
winLeft = DevToCSSIntPixels(winLeft);
|
|
winTop = DevToCSSIntPixels(winTop);
|
|
winWidth = DevToCSSIntPixels(winWidth);
|
|
winHeight = DevToCSSIntPixels(winHeight);
|
|
|
|
// Get the screen dimensions
|
|
// XXX This should use nsIScreenManager once it's fully fleshed out.
|
|
int32_t screenLeft = screen->GetAvailLeft(IgnoreErrors());
|
|
int32_t screenWidth = screen->GetAvailWidth(IgnoreErrors());
|
|
int32_t screenHeight = screen->GetAvailHeight(IgnoreErrors());
|
|
#if defined(XP_MACOSX)
|
|
/* The mac's coordinate system is different from the assumed Windows'
|
|
system. It offsets by the height of the menubar so that a window
|
|
placed at (0,0) will be entirely visible. Unfortunately that
|
|
correction is made elsewhere (in Widget) and the meaning of
|
|
the Avail... coordinates is overloaded. Here we allow a window
|
|
to be placed at (0,0) because it does make sense to do so.
|
|
*/
|
|
int32_t screenTop = screen->GetTop(IgnoreErrors());
|
|
#else
|
|
int32_t screenTop = screen->GetAvailTop(IgnoreErrors());
|
|
#endif
|
|
|
|
if (aLeft) {
|
|
if (screenLeft + screenWidth < *aLeft + winWidth)
|
|
*aLeft = screenLeft + screenWidth - winWidth;
|
|
if (screenLeft > *aLeft) *aLeft = screenLeft;
|
|
}
|
|
if (aTop) {
|
|
if (screenTop + screenHeight < *aTop + winHeight)
|
|
*aTop = screenTop + screenHeight - winHeight;
|
|
if (screenTop > *aTop) *aTop = screenTop;
|
|
}
|
|
} else {
|
|
if (aLeft) *aLeft = 0;
|
|
if (aTop) *aTop = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t nsGlobalWindowOuter::GetScrollBoundaryOuter(Side aSide) {
|
|
FlushPendingNotifications(FlushType::Layout);
|
|
if (nsIScrollableFrame* sf = GetScrollFrame()) {
|
|
return nsPresContext::AppUnitsToIntCSSPixels(
|
|
sf->GetScrollRange().Edge(aSide));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
CSSPoint nsGlobalWindowOuter::GetScrollXY(bool aDoFlush) {
|
|
if (aDoFlush) {
|
|
FlushPendingNotifications(FlushType::Layout);
|
|
} else {
|
|
EnsureSizeAndPositionUpToDate();
|
|
}
|
|
|
|
nsIScrollableFrame* sf = GetScrollFrame();
|
|
if (!sf) {
|
|
return CSSIntPoint(0, 0);
|
|
}
|
|
|
|
nsPoint scrollPos = sf->GetScrollPosition();
|
|
if (scrollPos != nsPoint(0, 0) && !aDoFlush) {
|
|
// Oh, well. This is the expensive case -- the window is scrolled and we
|
|
// didn't actually flush yet. Repeat, but with a flush, since the content
|
|
// may get shorter and hence our scroll position may decrease.
|
|
return GetScrollXY(true);
|
|
}
|
|
|
|
return CSSPoint::FromAppUnits(scrollPos);
|
|
}
|
|
|
|
double nsGlobalWindowOuter::GetScrollXOuter() { return GetScrollXY(false).x; }
|
|
|
|
double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }
|
|
|
|
uint32_t nsGlobalWindowOuter::Length() {
|
|
BrowsingContext* bc = GetBrowsingContext();
|
|
return bc ? bc->GetChildren().Length() : 0;
|
|
}
|
|
|
|
Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
|
|
BrowsingContext* bc = GetBrowsingContext();
|
|
return bc ? bc->GetTop(IgnoreErrors()) : nullptr;
|
|
}
|
|
|
|
already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
|
|
const nsAString& aName) {
|
|
NS_ENSURE_TRUE(mBrowsingContext, nullptr);
|
|
|
|
return do_AddRef(mBrowsingContext->FindChildWithName(aName));
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::DispatchCustomEvent(const nsAString& aEventName) {
|
|
bool defaultActionEnabled = true;
|
|
nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
|
|
CanBubble::eYes, Cancelable::eYes,
|
|
&defaultActionEnabled);
|
|
|
|
return defaultActionEnabled;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::DispatchResizeEvent(const CSSIntSize& aSize) {
|
|
ErrorResult res;
|
|
RefPtr<Event> domEvent = mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"),
|
|
CallerType::System, res);
|
|
if (res.Failed()) {
|
|
return false;
|
|
}
|
|
|
|
// We don't init the AutoJSAPI with ourselves because we don't want it
|
|
// reporting errors to our onerror handlers.
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
JSContext* cx = jsapi.cx();
|
|
JSAutoRealm ar(cx, GetWrapperPreserveColor());
|
|
|
|
DOMWindowResizeEventDetail detail;
|
|
detail.mWidth = aSize.width;
|
|
detail.mHeight = aSize.height;
|
|
JS::Rooted<JS::Value> detailValue(cx);
|
|
if (!ToJSValue(cx, detail, &detailValue)) {
|
|
return false;
|
|
}
|
|
|
|
CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
|
|
customEvent->InitCustomEvent(cx, NS_LITERAL_STRING("DOMWindowResize"),
|
|
/* aCanBubble = */ true,
|
|
/* aCancelable = */ true, detailValue);
|
|
|
|
domEvent->SetTrusted(true);
|
|
domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
|
|
|
|
nsCOMPtr<EventTarget> target = this;
|
|
domEvent->SetTarget(target);
|
|
|
|
return target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
|
|
}
|
|
|
|
static already_AddRefed<nsIDocShellTreeItem> GetCallerDocShellTreeItem() {
|
|
nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal());
|
|
nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav);
|
|
|
|
return callerItem.forget();
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::WindowExists(const nsAString& aName,
|
|
bool aForceNoOpener,
|
|
bool aLookForCallerOnJSStack) {
|
|
MOZ_ASSERT(mDocShell, "Must have docshell");
|
|
|
|
if (aForceNoOpener) {
|
|
return aName.LowerCaseEqualsLiteral("_self") ||
|
|
aName.LowerCaseEqualsLiteral("_top") ||
|
|
aName.LowerCaseEqualsLiteral("_parent");
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> caller;
|
|
if (aLookForCallerOnJSStack) {
|
|
caller = GetCallerDocShellTreeItem();
|
|
}
|
|
|
|
if (!caller) {
|
|
caller = mDocShell;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> namedItem;
|
|
mDocShell->FindItemWithName(aName, nullptr, caller,
|
|
/* aSkipTabGroup = */ false,
|
|
getter_AddRefs(namedItem));
|
|
return namedItem != nullptr;
|
|
}
|
|
|
|
already_AddRefed<nsIWidget> nsGlobalWindowOuter::GetMainWidget() {
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
|
|
nsCOMPtr<nsIWidget> widget;
|
|
|
|
if (treeOwnerAsWin) {
|
|
treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
|
|
}
|
|
|
|
return widget.forget();
|
|
}
|
|
|
|
nsIWidget* nsGlobalWindowOuter::GetNearestWidget() const {
|
|
nsIDocShell* docShell = GetDocShell();
|
|
NS_ENSURE_TRUE(docShell, nullptr);
|
|
PresShell* presShell = docShell->GetPresShell();
|
|
NS_ENSURE_TRUE(presShell, nullptr);
|
|
nsIFrame* rootFrame = presShell->GetRootFrame();
|
|
NS_ENSURE_TRUE(rootFrame, nullptr);
|
|
return rootFrame->GetView()->GetNearestWidget(nullptr);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetFullscreenOuter(bool aFullscreen,
|
|
mozilla::ErrorResult& aError) {
|
|
aError =
|
|
SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullscreen);
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::SetFullScreen(bool aFullscreen) {
|
|
return SetFullscreenInternal(FullscreenReason::ForFullscreenMode,
|
|
aFullscreen);
|
|
}
|
|
|
|
static void FinishDOMFullscreenChange(Document* aDoc, bool aInDOMFullscreen) {
|
|
if (aInDOMFullscreen) {
|
|
// Ask the document to handle any pending DOM fullscreen change.
|
|
if (!Document::HandlePendingFullscreenRequests(aDoc)) {
|
|
// If we don't end up having anything in fullscreen,
|
|
// async request exiting fullscreen.
|
|
Document::AsyncExitFullscreen(aDoc);
|
|
}
|
|
} else {
|
|
// If the window is leaving fullscreen state, also ask the document
|
|
// to exit from DOM Fullscreen.
|
|
Document::ExitFullscreenInDocTree(aDoc);
|
|
}
|
|
}
|
|
|
|
struct FullscreenTransitionDuration {
|
|
// The unit of the durations is millisecond
|
|
uint16_t mFadeIn = 0;
|
|
uint16_t mFadeOut = 0;
|
|
bool IsSuppressed() const { return mFadeIn == 0 && mFadeOut == 0; }
|
|
};
|
|
|
|
static void GetFullscreenTransitionDuration(
|
|
bool aEnterFullscreen, FullscreenTransitionDuration* aDuration) {
|
|
const char* pref = aEnterFullscreen
|
|
? "full-screen-api.transition-duration.enter"
|
|
: "full-screen-api.transition-duration.leave";
|
|
nsAutoCString prefValue;
|
|
Preferences::GetCString(pref, prefValue);
|
|
if (!prefValue.IsEmpty()) {
|
|
sscanf(prefValue.get(), "%hu%hu", &aDuration->mFadeIn,
|
|
&aDuration->mFadeOut);
|
|
}
|
|
}
|
|
|
|
class FullscreenTransitionTask : public Runnable {
|
|
public:
|
|
FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
|
|
nsGlobalWindowOuter* aWindow, bool aFullscreen,
|
|
nsIWidget* aWidget, nsIScreen* aScreen,
|
|
nsISupports* aTransitionData)
|
|
: mozilla::Runnable("FullscreenTransitionTask"),
|
|
mWindow(aWindow),
|
|
mWidget(aWidget),
|
|
mScreen(aScreen),
|
|
mTransitionData(aTransitionData),
|
|
mDuration(aDuration),
|
|
mStage(eBeforeToggle),
|
|
mFullscreen(aFullscreen) {}
|
|
|
|
NS_IMETHOD Run() override;
|
|
|
|
private:
|
|
~FullscreenTransitionTask() override {}
|
|
|
|
/**
|
|
* The flow of fullscreen transition:
|
|
*
|
|
* parent process | child process
|
|
* ----------------------------------------------------------------
|
|
*
|
|
* | request/exit fullscreen
|
|
* <-----|
|
|
* BeforeToggle stage |
|
|
* |
|
|
* ToggleFullscreen stage *1 |----->
|
|
* | HandleFullscreenRequests
|
|
* |
|
|
* <-----| MozAfterPaint event
|
|
* AfterToggle stage *2 |
|
|
* |
|
|
* End stage |
|
|
*
|
|
* Note we also start a timer at *1 so that if we don't get MozAfterPaint
|
|
* from the child process in time, we continue going to *2.
|
|
*/
|
|
enum Stage {
|
|
// BeforeToggle stage happens before we enter or leave fullscreen
|
|
// state. In this stage, the task triggers the pre-toggle fullscreen
|
|
// transition on the widget.
|
|
eBeforeToggle,
|
|
// ToggleFullscreen stage actually executes the fullscreen toggle,
|
|
// and wait for the next paint on the content to continue.
|
|
eToggleFullscreen,
|
|
// AfterToggle stage happens after we toggle the fullscreen state.
|
|
// In this stage, the task triggers the post-toggle fullscreen
|
|
// transition on the widget.
|
|
eAfterToggle,
|
|
// End stage is triggered after the final transition finishes.
|
|
eEnd
|
|
};
|
|
|
|
class Observer final : public nsIObserver {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
explicit Observer(FullscreenTransitionTask* aTask) : mTask(aTask) {}
|
|
|
|
private:
|
|
~Observer() = default;
|
|
|
|
RefPtr<FullscreenTransitionTask> mTask;
|
|
};
|
|
|
|
static const char* const kPaintedTopic;
|
|
|
|
RefPtr<nsGlobalWindowOuter> mWindow;
|
|
nsCOMPtr<nsIWidget> mWidget;
|
|
nsCOMPtr<nsIScreen> mScreen;
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
nsCOMPtr<nsISupports> mTransitionData;
|
|
|
|
TimeStamp mFullscreenChangeStartTime;
|
|
FullscreenTransitionDuration mDuration;
|
|
Stage mStage;
|
|
bool mFullscreen;
|
|
};
|
|
|
|
const char* const FullscreenTransitionTask::kPaintedTopic =
|
|
"fullscreen-painted";
|
|
|
|
NS_IMETHODIMP
|
|
FullscreenTransitionTask::Run() {
|
|
Stage stage = mStage;
|
|
mStage = Stage(mStage + 1);
|
|
if (MOZ_UNLIKELY(mWidget->Destroyed())) {
|
|
// If the widget has been destroyed before we get here, don't try to
|
|
// do anything more. Just let it go and release ourselves.
|
|
NS_WARNING("The widget to fullscreen has been destroyed");
|
|
return NS_OK;
|
|
}
|
|
if (stage == eBeforeToggle) {
|
|
PROFILER_ADD_MARKER("Fullscreen transition start", DOM);
|
|
mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
|
|
mDuration.mFadeIn, mTransitionData,
|
|
this);
|
|
} else if (stage == eToggleFullscreen) {
|
|
PROFILER_ADD_MARKER("Fullscreen toggle start", DOM);
|
|
mFullscreenChangeStartTime = TimeStamp::Now();
|
|
if (MOZ_UNLIKELY(mWindow->mFullscreen != mFullscreen)) {
|
|
// This could happen in theory if several fullscreen requests in
|
|
// different direction happen continuously in a short time. We
|
|
// need to ensure the fullscreen state matches our target here,
|
|
// otherwise the widget would change the window state as if we
|
|
// toggle for Fullscreen Mode instead of Fullscreen API.
|
|
NS_WARNING("The fullscreen state of the window does not match");
|
|
mWindow->mFullscreen = mFullscreen;
|
|
}
|
|
// Toggle the fullscreen state on the widget
|
|
if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI,
|
|
mFullscreen, mWidget, mScreen)) {
|
|
// Fail to setup the widget, call FinishFullscreenChange to
|
|
// complete fullscreen change directly.
|
|
mWindow->FinishFullscreenChange(mFullscreen);
|
|
}
|
|
// Set observer for the next content paint.
|
|
nsCOMPtr<nsIObserver> observer = new Observer(this);
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
obs->AddObserver(observer, kPaintedTopic, false);
|
|
// There are several edge cases where we may never get the paint
|
|
// notification, including:
|
|
// 1. the window/tab is closed before the next paint;
|
|
// 2. the user has switched to another tab before we get here.
|
|
// Completely fixing those cases seems to be tricky, and since they
|
|
// should rarely happen, it probably isn't worth to fix. Hence we
|
|
// simply add a timeout here to ensure we never hang forever.
|
|
// In addition, if the page is complicated or the machine is less
|
|
// powerful, layout could take a long time, in which case, staying
|
|
// in black screen for that long could hurt user experience even
|
|
// more than exposing an intermediate state.
|
|
uint32_t timeout =
|
|
Preferences::GetUint("full-screen-api.transition.timeout", 1000);
|
|
NS_NewTimerWithObserver(getter_AddRefs(mTimer), observer, timeout,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
} else if (stage == eAfterToggle) {
|
|
Telemetry::AccumulateTimeDelta(Telemetry::FULLSCREEN_TRANSITION_BLACK_MS,
|
|
mFullscreenChangeStartTime);
|
|
mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
|
|
mDuration.mFadeOut, mTransitionData,
|
|
this);
|
|
} else if (stage == eEnd) {
|
|
PROFILER_ADD_MARKER("Fullscreen transition end", DOM);
|
|
mWidget->CleanupFullscreenTransition();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver)
|
|
|
|
NS_IMETHODIMP
|
|
FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData) {
|
|
bool shouldContinue = false;
|
|
if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
|
|
nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject));
|
|
nsCOMPtr<nsIWidget> widget =
|
|
win ? nsGlobalWindowInner::Cast(win)->GetMainWidget() : nullptr;
|
|
if (widget == mTask->mWidget) {
|
|
// The paint notification arrives first. Cancel the timer.
|
|
mTask->mTimer->Cancel();
|
|
shouldContinue = true;
|
|
PROFILER_ADD_MARKER("Fullscreen toggle end", DOM);
|
|
}
|
|
} else {
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
|
|
"Should only get fullscreen-painted or timer-callback");
|
|
nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
|
|
MOZ_ASSERT(timer && timer == mTask->mTimer,
|
|
"Should only trigger this with the timer the task created");
|
|
#endif
|
|
shouldContinue = true;
|
|
PROFILER_ADD_MARKER("Fullscreen toggle timeout", DOM);
|
|
}
|
|
if (shouldContinue) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
obs->RemoveObserver(this, kPaintedTopic);
|
|
mTask->mTimer = nullptr;
|
|
mTask->Run();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
static bool MakeWidgetFullscreen(nsGlobalWindowOuter* aWindow,
|
|
FullscreenReason aReason, bool aFullscreen) {
|
|
nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
|
|
if (!widget) {
|
|
return false;
|
|
}
|
|
|
|
FullscreenTransitionDuration duration;
|
|
bool performTransition = false;
|
|
nsCOMPtr<nsISupports> transitionData;
|
|
if (aReason == FullscreenReason::ForFullscreenAPI) {
|
|
GetFullscreenTransitionDuration(aFullscreen, &duration);
|
|
if (!duration.IsSuppressed()) {
|
|
performTransition = widget->PrepareForFullscreenTransition(
|
|
getter_AddRefs(transitionData));
|
|
}
|
|
}
|
|
// We pass nullptr as the screen to SetWidgetFullscreen
|
|
// and FullscreenTransitionTask, as we do not wish to override
|
|
// the default screen selection behavior. The screen containing
|
|
// most of the widget will be selected.
|
|
if (!performTransition) {
|
|
return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget, nullptr);
|
|
}
|
|
nsCOMPtr<nsIRunnable> task = new FullscreenTransitionTask(
|
|
duration, aWindow, aFullscreen, widget, nullptr, transitionData);
|
|
task->Run();
|
|
return true;
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::SetFullscreenInternal(FullscreenReason aReason,
|
|
bool aFullscreen) {
|
|
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
|
|
"Requires safe to run script as it "
|
|
"may call FinishDOMFullscreenChange");
|
|
|
|
NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
|
|
|
|
MOZ_ASSERT(
|
|
aReason != FullscreenReason::ForForceExitFullscreen || !aFullscreen,
|
|
"FullscreenReason::ForForceExitFullscreen can "
|
|
"only be used with exiting fullscreen");
|
|
|
|
// Only chrome can change our fullscreen mode. Otherwise, the state
|
|
// can only be changed for DOM fullscreen.
|
|
if (aReason == FullscreenReason::ForFullscreenMode &&
|
|
!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// SetFullscreen needs to be called on the root window, so get that
|
|
// via the DocShell tree, and if we are not already the root,
|
|
// call SetFullscreen on that window instead.
|
|
nsCOMPtr<nsIDocShellTreeItem> rootItem;
|
|
mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
|
|
nsCOMPtr<nsPIDOMWindowOuter> window =
|
|
rootItem ? rootItem->GetWindow() : nullptr;
|
|
if (!window) return NS_ERROR_FAILURE;
|
|
if (rootItem != mDocShell)
|
|
return window->SetFullscreenInternal(aReason, aFullscreen);
|
|
|
|
// make sure we don't try to set full screen on a non-chrome window,
|
|
// which might happen in embedding world
|
|
if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// If we are already in full screen mode, just return.
|
|
if (mFullscreen == aFullscreen) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Note that although entering DOM fullscreen could also cause
|
|
// consequential calls to this method, those calls will be skipped
|
|
// at the condition above.
|
|
if (aReason == FullscreenReason::ForFullscreenMode) {
|
|
if (!aFullscreen && !mFullscreenMode) {
|
|
// If we are exiting fullscreen mode, but we actually didn't
|
|
// entered fullscreen mode, the fullscreen state was only for
|
|
// the Fullscreen API. Change the reason here so that we can
|
|
// perform transition for it.
|
|
aReason = FullscreenReason::ForFullscreenAPI;
|
|
} else {
|
|
mFullscreenMode = aFullscreen;
|
|
}
|
|
} else {
|
|
// If we are exiting from DOM fullscreen while we initially make
|
|
// the window fullscreen because of fullscreen mode, don't restore
|
|
// the window. But we still need to exit the DOM fullscreen state.
|
|
if (!aFullscreen && mFullscreenMode) {
|
|
FinishDOMFullscreenChange(mDoc, false);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// Prevent chrome documents which are still loading from resizing
|
|
// the window after we set fullscreen mode.
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwnerAsWin));
|
|
if (aFullscreen && xulWin) {
|
|
xulWin->SetIntrinsicallySized(false);
|
|
}
|
|
|
|
// Set this before so if widget sends an event indicating its
|
|
// gone full screen, the state trap above works.
|
|
mFullscreen = aFullscreen;
|
|
|
|
// Sometimes we don't want the top-level widget to actually go fullscreen,
|
|
// for example in the B2G desktop client, we don't want the emulated screen
|
|
// dimensions to appear to increase when entering fullscreen mode; we just
|
|
// want the content to fill the entire client area of the emulator window.
|
|
if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
|
|
if (MakeWidgetFullscreen(this, aReason, aFullscreen)) {
|
|
// The rest of code for switching fullscreen is in nsGlobalWindowOuter::
|
|
// FinishFullscreenChange() which will be called after sizemodechange
|
|
// event is dispatched.
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
FinishFullscreenChange(aFullscreen);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::SetWidgetFullscreen(FullscreenReason aReason,
|
|
bool aIsFullscreen,
|
|
nsIWidget* aWidget,
|
|
nsIScreen* aScreen) {
|
|
MOZ_ASSERT(this == GetTopInternal(), "Only topmost window should call this");
|
|
MOZ_ASSERT(!GetFrameElementInternal(), "Content window should not call this");
|
|
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
|
|
|
|
if (!NS_WARN_IF(!IsChromeWindow())) {
|
|
if (!NS_WARN_IF(mChromeFields.mFullscreenPresShell)) {
|
|
if (PresShell* presShell = mDocShell->GetPresShell()) {
|
|
if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
|
|
mChromeFields.mFullscreenPresShell = do_GetWeakReference(presShell);
|
|
MOZ_ASSERT(mChromeFields.mFullscreenPresShell);
|
|
rd->SetIsResizeSuppressed();
|
|
rd->Freeze();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nsresult rv =
|
|
aReason == FullscreenReason::ForFullscreenMode
|
|
?
|
|
// If we enter fullscreen for fullscreen mode, we want
|
|
// the native system behavior.
|
|
aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen, aScreen)
|
|
: aWidget->MakeFullScreen(aIsFullscreen, aScreen);
|
|
return NS_SUCCEEDED(rv);
|
|
}
|
|
|
|
/* virtual */
|
|
void nsGlobalWindowOuter::FullscreenWillChange(bool aIsFullscreen) {
|
|
if (aIsFullscreen) {
|
|
DispatchCustomEvent(NS_LITERAL_STRING("willenterfullscreen"));
|
|
} else {
|
|
DispatchCustomEvent(NS_LITERAL_STRING("willexitfullscreen"));
|
|
}
|
|
}
|
|
|
|
/* virtual */
|
|
void nsGlobalWindowOuter::FinishFullscreenChange(bool aIsFullscreen) {
|
|
if (aIsFullscreen != mFullscreen) {
|
|
NS_WARNING("Failed to toggle fullscreen state of the widget");
|
|
// We failed to make the widget enter fullscreen.
|
|
// Stop further changes and restore the state.
|
|
if (!aIsFullscreen) {
|
|
mFullscreen = false;
|
|
mFullscreenMode = false;
|
|
} else {
|
|
MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?");
|
|
mFullscreen = true;
|
|
// We don't know how code can reach here. Not sure
|
|
// what value should be set for fullscreen mode.
|
|
mFullscreenMode = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Note that we must call this to toggle the DOM fullscreen state
|
|
// of the document before dispatching the "fullscreen" event, so
|
|
// that the chrome can distinguish between browser fullscreen mode
|
|
// and DOM fullscreen.
|
|
FinishDOMFullscreenChange(mDoc, mFullscreen);
|
|
|
|
// dispatch a "fullscreen" DOM event so that XUL apps can
|
|
// respond visually if we are kicked into full screen mode
|
|
DispatchCustomEvent(NS_LITERAL_STRING("fullscreen"));
|
|
|
|
if (!NS_WARN_IF(!IsChromeWindow())) {
|
|
if (RefPtr<PresShell> presShell =
|
|
do_QueryReferent(mChromeFields.mFullscreenPresShell)) {
|
|
if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
|
|
rd->Thaw();
|
|
}
|
|
mChromeFields.mFullscreenPresShell = nullptr;
|
|
}
|
|
}
|
|
|
|
if (!mWakeLock && mFullscreen) {
|
|
RefPtr<power::PowerManagerService> pmService =
|
|
power::PowerManagerService::GetInstance();
|
|
if (!pmService) {
|
|
return;
|
|
}
|
|
|
|
// XXXkhuey using the inner here, do we need to do something if it changes?
|
|
ErrorResult rv;
|
|
mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("DOM_Fullscreen"),
|
|
GetCurrentInnerWindow(), rv);
|
|
NS_WARNING_ASSERTION(!rv.Failed(), "Failed to lock the wakelock");
|
|
rv.SuppressException();
|
|
} else if (mWakeLock && !mFullscreen) {
|
|
ErrorResult rv;
|
|
mWakeLock->Unlock(rv);
|
|
mWakeLock = nullptr;
|
|
rv.SuppressException();
|
|
}
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::Fullscreen() const {
|
|
NS_ENSURE_TRUE(mDocShell, mFullscreen);
|
|
|
|
// Get the fullscreen value of the root window, to always have the value
|
|
// accurate, even when called from content.
|
|
nsCOMPtr<nsIDocShellTreeItem> rootItem;
|
|
mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
|
|
if (rootItem == mDocShell) {
|
|
if (!XRE_IsContentProcess()) {
|
|
// We are the root window. Return our internal value.
|
|
return mFullscreen;
|
|
}
|
|
if (nsCOMPtr<nsIWidget> widget = GetNearestWidget()) {
|
|
// We are in content process, figure out the value from
|
|
// the sizemode of the puppet widget.
|
|
return widget->SizeMode() == nsSizeMode_Fullscreen;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> window = rootItem->GetWindow();
|
|
NS_ENSURE_TRUE(window, mFullscreen);
|
|
|
|
return nsGlobalWindowOuter::Cast(window)->Fullscreen();
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::GetFullscreenOuter() { return Fullscreen(); }
|
|
|
|
bool nsGlobalWindowOuter::GetFullScreen() {
|
|
FORWARD_TO_INNER(GetFullScreen, (), false);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::EnsureReflowFlushAndPaint() {
|
|
NS_ASSERTION(mDocShell,
|
|
"EnsureReflowFlushAndPaint() called with no "
|
|
"docshell!");
|
|
|
|
if (!mDocShell) return;
|
|
|
|
RefPtr<PresShell> presShell = mDocShell->GetPresShell();
|
|
if (!presShell) {
|
|
return;
|
|
}
|
|
|
|
// Flush pending reflows.
|
|
if (mDoc) {
|
|
mDoc->FlushPendingNotifications(FlushType::Layout);
|
|
}
|
|
|
|
// Unsuppress painting.
|
|
presShell->UnsuppressPainting();
|
|
}
|
|
|
|
// static
|
|
void nsGlobalWindowOuter::MakeScriptDialogTitle(
|
|
nsAString& aOutTitle, nsIPrincipal* aSubjectPrincipal) {
|
|
MOZ_ASSERT(aSubjectPrincipal);
|
|
|
|
aOutTitle.Truncate();
|
|
|
|
// Try to get a host from the running principal -- this will do the
|
|
// right thing for javascript: and data: documents.
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = aSubjectPrincipal->GetURI(getter_AddRefs(uri));
|
|
if (NS_SUCCEEDED(rv) && uri) {
|
|
// remove user:pass for privacy and spoof prevention
|
|
|
|
nsCOMPtr<nsIURIFixup> fixup(components::URIFixup::Service());
|
|
if (fixup) {
|
|
nsCOMPtr<nsIURI> fixedURI;
|
|
rv = fixup->CreateExposableURI(uri, getter_AddRefs(fixedURI));
|
|
if (NS_SUCCEEDED(rv) && fixedURI) {
|
|
nsAutoCString host;
|
|
fixedURI->GetHost(host);
|
|
|
|
if (!host.IsEmpty()) {
|
|
// if this URI has a host we'll show it. For other
|
|
// schemes (e.g. file:) we fall back to the localized
|
|
// generic string
|
|
|
|
nsAutoCString prepath;
|
|
fixedURI->GetDisplayPrePath(prepath);
|
|
|
|
NS_ConvertUTF8toUTF16 ucsPrePath(prepath);
|
|
nsContentUtils::FormatLocalizedString(
|
|
aOutTitle, nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
|
|
"ScriptDlgHeading", ucsPrePath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aOutTitle.IsEmpty()) {
|
|
// We didn't find a host so use the generic heading
|
|
nsContentUtils::GetLocalizedString(
|
|
nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDlgGenericHeading",
|
|
aOutTitle);
|
|
}
|
|
|
|
// Just in case
|
|
if (aOutTitle.IsEmpty()) {
|
|
NS_WARNING(
|
|
"could not get ScriptDlgGenericHeading string from string bundle");
|
|
aOutTitle.AssignLiteral("[Script]");
|
|
}
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::CanMoveResizeWindows(CallerType aCallerType) {
|
|
// When called from chrome, we can avoid the following checks.
|
|
if (aCallerType != CallerType::System) {
|
|
// Don't allow scripts to move or resize windows that were not opened by a
|
|
// script.
|
|
if (!mHadOriginalOpener) {
|
|
return false;
|
|
}
|
|
|
|
if (!CanSetProperty("dom.disable_window_move_resize")) {
|
|
return false;
|
|
}
|
|
|
|
// Ignore the request if we have more than one tab in the window.
|
|
if (XRE_IsContentProcess()) {
|
|
nsCOMPtr<nsIDocShell> docShell = GetDocShell();
|
|
if (docShell) {
|
|
nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild();
|
|
bool hasSiblings = true;
|
|
if (child && NS_SUCCEEDED(child->GetHasSiblings(&hasSiblings)) &&
|
|
hasSiblings) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
|
|
uint32_t itemCount = 0;
|
|
if (treeOwner && NS_SUCCEEDED(treeOwner->GetTabCount(&itemCount)) &&
|
|
itemCount > 1) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mDocShell) {
|
|
bool allow;
|
|
nsresult rv = mDocShell->GetAllowWindowControl(&allow);
|
|
if (NS_SUCCEEDED(rv) && !allow) return false;
|
|
}
|
|
|
|
if (nsGlobalWindowInner::sMouseDown &&
|
|
!nsGlobalWindowInner::sDragServiceDisabled) {
|
|
nsCOMPtr<nsIDragService> ds =
|
|
do_GetService("@mozilla.org/widget/dragservice;1");
|
|
if (ds) {
|
|
nsGlobalWindowInner::sDragServiceDisabled = true;
|
|
ds->Suppress();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::AlertOrConfirm(bool aAlert, const nsAString& aMessage,
|
|
nsIPrincipal& aSubjectPrincipal,
|
|
ErrorResult& aError) {
|
|
// XXX This method is very similar to nsGlobalWindowOuter::Prompt, make
|
|
// sure any modifications here don't need to happen over there!
|
|
if (!AreDialogsEnabled()) {
|
|
// Just silently return. In the case of alert(), the return value is
|
|
// ignored. In the case of confirm(), returning false is the same thing as
|
|
// would happen if the user cancels.
|
|
return false;
|
|
}
|
|
|
|
// 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.
|
|
AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
|
|
|
|
// Before bringing up the window, unsuppress painting and flush
|
|
// pending reflows.
|
|
EnsureReflowFlushAndPaint();
|
|
|
|
nsAutoString title;
|
|
MakeScriptDialogTitle(title, &aSubjectPrincipal);
|
|
|
|
// Remove non-terminating null characters from the
|
|
// string. See bug #310037.
|
|
nsAutoString final;
|
|
nsContentUtils::StripNullChars(aMessage, final);
|
|
nsContentUtils::PlatformToDOMLineBreaks(final);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPromptFactory> promptFac =
|
|
do_GetService("@mozilla.org/prompter;1", &rv);
|
|
if (NS_FAILED(rv)) {
|
|
aError.Throw(rv);
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrompt> prompt;
|
|
aError =
|
|
promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
|
|
if (aError.Failed()) {
|
|
return false;
|
|
}
|
|
|
|
// Always allow tab modal prompts for alert and confirm.
|
|
if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
|
|
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
|
|
}
|
|
|
|
bool result = false;
|
|
nsAutoSyncOperation sync(mDoc);
|
|
if (ShouldPromptToBlockDialogs()) {
|
|
bool disallowDialog = false;
|
|
nsAutoString label;
|
|
nsContentUtils::GetLocalizedString(
|
|
nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label);
|
|
|
|
aError = aAlert
|
|
? prompt->AlertCheck(title.get(), final.get(), label.get(),
|
|
&disallowDialog)
|
|
: prompt->ConfirmCheck(title.get(), final.get(), label.get(),
|
|
&disallowDialog, &result);
|
|
|
|
if (disallowDialog) DisableDialogs();
|
|
} else {
|
|
aError = aAlert ? prompt->Alert(title.get(), final.get())
|
|
: prompt->Confirm(title.get(), final.get(), &result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::AlertOuter(const nsAString& aMessage,
|
|
nsIPrincipal& aSubjectPrincipal,
|
|
ErrorResult& aError) {
|
|
AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError);
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::ConfirmOuter(const nsAString& aMessage,
|
|
nsIPrincipal& aSubjectPrincipal,
|
|
ErrorResult& aError) {
|
|
return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal,
|
|
aError);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage,
|
|
const nsAString& aInitial,
|
|
nsAString& aReturn,
|
|
nsIPrincipal& aSubjectPrincipal,
|
|
ErrorResult& aError) {
|
|
// XXX This method is very similar to nsGlobalWindowOuter::AlertOrConfirm,
|
|
// make sure any modifications here don't need to happen over there!
|
|
SetDOMStringToNull(aReturn);
|
|
|
|
if (!AreDialogsEnabled()) {
|
|
// Return null, as if the user just canceled the prompt.
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
|
|
|
|
// Before bringing up the window, unsuppress painting and flush
|
|
// pending reflows.
|
|
EnsureReflowFlushAndPaint();
|
|
|
|
nsAutoString title;
|
|
MakeScriptDialogTitle(title, &aSubjectPrincipal);
|
|
|
|
// Remove non-terminating null characters from the
|
|
// string. See bug #310037.
|
|
nsAutoString fixedMessage, fixedInitial;
|
|
nsContentUtils::StripNullChars(aMessage, fixedMessage);
|
|
nsContentUtils::PlatformToDOMLineBreaks(fixedMessage);
|
|
nsContentUtils::StripNullChars(aInitial, fixedInitial);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPromptFactory> promptFac =
|
|
do_GetService("@mozilla.org/prompter;1", &rv);
|
|
if (NS_FAILED(rv)) {
|
|
aError.Throw(rv);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrompt> prompt;
|
|
aError =
|
|
promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
// Always allow tab modal prompts for prompt.
|
|
if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
|
|
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
|
|
}
|
|
|
|
// Pass in the default value, if any.
|
|
char16_t* inoutValue = ToNewUnicode(fixedInitial);
|
|
bool disallowDialog = false;
|
|
|
|
nsAutoString label;
|
|
label.SetIsVoid(true);
|
|
if (ShouldPromptToBlockDialogs()) {
|
|
nsContentUtils::GetLocalizedString(
|
|
nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label);
|
|
}
|
|
|
|
nsAutoSyncOperation sync(mDoc);
|
|
bool ok;
|
|
aError = prompt->Prompt(title.get(), fixedMessage.get(), &inoutValue,
|
|
label.IsVoid() ? nullptr : label.get(),
|
|
&disallowDialog, &ok);
|
|
|
|
if (disallowDialog) {
|
|
DisableDialogs();
|
|
}
|
|
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
nsString outValue;
|
|
outValue.Adopt(inoutValue);
|
|
|
|
if (ok && inoutValue) {
|
|
aReturn.Assign(outValue);
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::FocusOuter() {
|
|
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (!fm) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
|
|
if (!baseWin) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
|
|
nsPIDOMWindowOuter* callerOuter = caller ? caller->GetOuterWindow() : nullptr;
|
|
nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpener();
|
|
|
|
// Enforce dom.disable_window_flip (for non-chrome), but still allow the
|
|
// window which opened us to raise us at times when popups are allowed
|
|
// (bugs 355482 and 369306).
|
|
bool canFocus = CanSetProperty("dom.disable_window_flip") ||
|
|
(opener == callerOuter &&
|
|
RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
|
|
PopupBlocker::openBlocked);
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> activeDOMWindow;
|
|
fm->GetActiveWindow(getter_AddRefs(activeDOMWindow));
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> rootItem;
|
|
mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
|
|
nsCOMPtr<nsPIDOMWindowOuter> rootWin =
|
|
rootItem ? rootItem->GetWindow() : nullptr;
|
|
auto* activeWindow = nsPIDOMWindowOuter::From(activeDOMWindow);
|
|
bool isActive = (rootWin == activeWindow);
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (treeOwnerAsWin && (canFocus || isActive)) {
|
|
bool isEnabled = true;
|
|
if (NS_SUCCEEDED(treeOwnerAsWin->GetEnabled(&isEnabled)) && !isEnabled) {
|
|
NS_WARNING("Should not try to set the focus on a disabled window");
|
|
return;
|
|
}
|
|
|
|
// XXXndeakin not sure what this is for or if it should go somewhere else
|
|
nsCOMPtr<nsIEmbeddingSiteWindow> embeddingWin(
|
|
do_GetInterface(treeOwnerAsWin));
|
|
if (embeddingWin) embeddingWin->SetFocus();
|
|
}
|
|
|
|
if (!mDocShell) {
|
|
return;
|
|
}
|
|
|
|
// Don't look for a presshell if we're a root chrome window that's got
|
|
// about:blank loaded. We don't want to focus our widget in that case.
|
|
// XXXbz should we really be checking for IsInitialDocument() instead?
|
|
nsCOMPtr<nsIDocShellTreeItem> parentDsti;
|
|
mDocShell->GetParent(getter_AddRefs(parentDsti));
|
|
|
|
// set the parent's current focus to the frame containing this window.
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent =
|
|
parentDsti ? parentDsti->GetWindow() : nullptr;
|
|
if (parent) {
|
|
nsCOMPtr<Document> parentdoc = parent->GetDoc();
|
|
if (!parentdoc) {
|
|
return;
|
|
}
|
|
|
|
if (Element* frame = parentdoc->FindContentForSubDocument(mDoc)) {
|
|
nsContentUtils::RequestFrameFocus(*frame, canFocus);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (canFocus) {
|
|
// if there is no parent, this must be a toplevel window, so raise the
|
|
// window if canFocus is true. If this is a child process, the raise
|
|
// window request will get forwarded to the parent by the puppet widget.
|
|
DebugOnly<nsresult> rv = fm->SetActiveWindow(this);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv),
|
|
"SetActiveWindow only fails if passed null or a non-toplevel "
|
|
"window, which is not the case here.");
|
|
}
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::Focus() {
|
|
FORWARD_TO_INNER(Focus, (), NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::BlurOuter() {
|
|
// If dom.disable_window_flip == true, then content should not be allowed
|
|
// to call this function (this would allow popunders, bug 369306)
|
|
if (!CanSetProperty("dom.disable_window_flip")) {
|
|
return;
|
|
}
|
|
|
|
// If embedding apps don't implement nsIEmbeddingSiteWindow, we
|
|
// shouldn't throw exceptions to web content.
|
|
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
|
|
nsCOMPtr<nsIEmbeddingSiteWindow> siteWindow(do_GetInterface(treeOwner));
|
|
if (siteWindow) {
|
|
// This method call may cause mDocShell to become nullptr.
|
|
siteWindow->Blur();
|
|
|
|
// if the root is focused, clear the focus
|
|
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (fm && mDoc) {
|
|
RefPtr<Element> element;
|
|
fm->GetFocusedElementForWindow(this, false, nullptr,
|
|
getter_AddRefs(element));
|
|
if (element == mDoc->GetRootElement()) {
|
|
fm->ClearFocus(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::StopOuter(ErrorResult& aError) {
|
|
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
|
|
if (webNav) {
|
|
aError = webNav->Stop(nsIWebNavigation::STOP_ALL);
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::PrintOuter(ErrorResult& aError) {
|
|
#ifdef NS_PRINTING
|
|
if (!AreDialogsEnabled()) {
|
|
// We probably want to keep throwing here; silently doing nothing is a bit
|
|
// weird given the typical use cases of print().
|
|
aError.Throw(NS_ERROR_NOT_AVAILABLE);
|
|
return;
|
|
}
|
|
|
|
if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) {
|
|
aError.Throw(NS_ERROR_NOT_AVAILABLE);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint;
|
|
if (NS_SUCCEEDED(GetInterface(NS_GET_IID(nsIWebBrowserPrint),
|
|
getter_AddRefs(webBrowserPrint)))) {
|
|
nsAutoSyncOperation sync(GetCurrentInnerWindowInternal()
|
|
? GetCurrentInnerWindowInternal()->mDoc.get()
|
|
: nullptr);
|
|
|
|
nsCOMPtr<nsIPrintSettingsService> printSettingsService =
|
|
do_GetService("@mozilla.org/gfx/printsettings-service;1");
|
|
|
|
nsCOMPtr<nsIPrintSettings> printSettings;
|
|
if (printSettingsService) {
|
|
bool printSettingsAreGlobal =
|
|
Preferences::GetBool("print.use_global_printsettings", false);
|
|
|
|
if (printSettingsAreGlobal) {
|
|
printSettingsService->GetGlobalPrintSettings(
|
|
getter_AddRefs(printSettings));
|
|
|
|
nsAutoString printerName;
|
|
printSettings->GetPrinterName(printerName);
|
|
|
|
bool shouldGetDefaultPrinterName = printerName.IsEmpty();
|
|
# ifdef MOZ_X11
|
|
// In Linux, GTK backend does not support per printer settings.
|
|
// Calling GetDefaultPrinterName causes a sandbox violation (see Bug
|
|
// 1329216). The printer name is not needed anywhere else on Linux
|
|
// before it gets to the parent. In the parent, we will then query the
|
|
// default printer name if no name is set. Unless we are in the parent,
|
|
// we will skip this part.
|
|
if (!XRE_IsParentProcess()) {
|
|
shouldGetDefaultPrinterName = false;
|
|
}
|
|
# endif
|
|
if (shouldGetDefaultPrinterName) {
|
|
printSettingsService->GetDefaultPrinterName(printerName);
|
|
printSettings->SetPrinterName(printerName);
|
|
}
|
|
printSettingsService->InitPrintSettingsFromPrinter(printerName,
|
|
printSettings);
|
|
printSettingsService->InitPrintSettingsFromPrefs(
|
|
printSettings, true, nsIPrintSettings::kInitSaveAll);
|
|
} else {
|
|
printSettingsService->GetNewPrintSettings(
|
|
getter_AddRefs(printSettings));
|
|
}
|
|
|
|
EnterModalState();
|
|
webBrowserPrint->Print(printSettings, nullptr);
|
|
LeaveModalState();
|
|
|
|
bool savePrintSettings =
|
|
Preferences::GetBool("print.save_print_settings", false);
|
|
if (printSettingsAreGlobal && savePrintSettings) {
|
|
printSettingsService->SavePrintSettingsToPrefs(
|
|
printSettings, true, nsIPrintSettings::kInitSaveAll);
|
|
printSettingsService->SavePrintSettingsToPrefs(
|
|
printSettings, false, nsIPrintSettings::kInitSavePrinterName);
|
|
}
|
|
} else {
|
|
webBrowserPrint->GetGlobalPrintSettings(getter_AddRefs(printSettings));
|
|
webBrowserPrint->Print(printSettings, nullptr);
|
|
}
|
|
}
|
|
#endif // NS_PRINTING
|
|
}
|
|
|
|
void nsGlobalWindowOuter::MoveToOuter(int32_t aXPos, int32_t aYPos,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
/*
|
|
* If caller is not chrome and the user has not explicitly exempted the site,
|
|
* prevent window.moveTo() by exiting early
|
|
*/
|
|
|
|
if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIScreenManager> screenMgr =
|
|
do_GetService("@mozilla.org/gfx/screenmanager;1");
|
|
nsCOMPtr<nsIScreen> screen;
|
|
if (screenMgr) {
|
|
CSSIntSize size;
|
|
GetInnerSize(size);
|
|
screenMgr->ScreenForRect(aXPos, aYPos, size.width, size.height,
|
|
getter_AddRefs(screen));
|
|
}
|
|
|
|
if (screen) {
|
|
// On secondary displays, the "CSS px" coordinates are offset so that they
|
|
// share their origin with global desktop pixels, to avoid ambiguities in
|
|
// the coordinate space when there are displays with different DPIs.
|
|
// (See the corresponding code in GetScreenXY() above.)
|
|
int32_t screenLeftDeskPx, screenTopDeskPx, w, h;
|
|
screen->GetRectDisplayPix(&screenLeftDeskPx, &screenTopDeskPx, &w, &h);
|
|
CSSIntPoint cssPos(aXPos - screenLeftDeskPx, aYPos - screenTopDeskPx);
|
|
CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerType);
|
|
|
|
double scale;
|
|
screen->GetDefaultCSSScaleFactor(&scale);
|
|
LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(scale);
|
|
|
|
screen->GetContentsScaleFactor(&scale);
|
|
DesktopPoint deskPos = devPos / DesktopToLayoutDeviceScale(scale);
|
|
aError = treeOwnerAsWin->SetPositionDesktopPix(screenLeftDeskPx + deskPos.x,
|
|
screenTopDeskPx + deskPos.y);
|
|
} else {
|
|
// We couldn't find a screen? Just assume a 1:1 mapping.
|
|
CSSIntPoint cssPos(aXPos, aXPos);
|
|
CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerType);
|
|
LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(1.0);
|
|
aError = treeOwnerAsWin->SetPosition(devPos.x, devPos.y);
|
|
}
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::MoveByOuter(int32_t aXDif, int32_t aYDif,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
/*
|
|
* If caller is not chrome and the user has not explicitly exempted the site,
|
|
* prevent window.moveBy() by exiting early
|
|
*/
|
|
|
|
if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// To do this correctly we have to convert what we get from GetPosition
|
|
// into CSS pixels, add the arguments, do the security check, and
|
|
// then convert back to device pixels for the call to SetPosition.
|
|
|
|
int32_t x, y;
|
|
aError = treeOwnerAsWin->GetPosition(&x, &y);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
// mild abuse of a "size" object so we don't need more helper functions
|
|
nsIntSize cssPos(DevToCSSIntPixels(nsIntSize(x, y)));
|
|
|
|
cssPos.width += aXDif;
|
|
cssPos.height += aYDif;
|
|
|
|
CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerType);
|
|
|
|
nsIntSize newDevPos(CSSToDevIntPixels(cssPos));
|
|
|
|
aError = treeOwnerAsWin->SetPosition(newDevPos.width, newDevPos.height);
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::MoveBy(int32_t aXDif, int32_t aYDif) {
|
|
ErrorResult rv;
|
|
MoveByOuter(aXDif, aYDif, CallerType::System, rv);
|
|
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ResizeToOuter(int32_t aWidth, int32_t aHeight,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
/*
|
|
* If caller is a browser-element then dispatch a resize event to
|
|
* the embedder.
|
|
*/
|
|
if (mDocShell && mDocShell->GetIsMozBrowser()) {
|
|
CSSIntSize size(aWidth, aHeight);
|
|
if (!DispatchResizeEvent(size)) {
|
|
// The embedder chose to prevent the default action for this
|
|
// event, so let's not resize this window after all...
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If caller is not chrome and the user has not explicitly exempted the site,
|
|
* prevent window.resizeTo() by exiting early
|
|
*/
|
|
|
|
if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
nsIntSize cssSize(aWidth, aHeight);
|
|
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
|
|
|
|
nsIntSize devSz(CSSToDevIntPixels(cssSize));
|
|
|
|
aError = treeOwnerAsWin->SetSize(devSz.width, devSz.height, true);
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
|
|
CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
/*
|
|
* If caller is a browser-element then dispatch a resize event to
|
|
* parent.
|
|
*/
|
|
if (mDocShell && mDocShell->GetIsMozBrowser()) {
|
|
CSSIntSize size;
|
|
if (NS_FAILED(GetInnerSize(size))) {
|
|
return;
|
|
}
|
|
|
|
size.width += aWidthDif;
|
|
size.height += aHeightDif;
|
|
|
|
if (!DispatchResizeEvent(size)) {
|
|
// The embedder chose to prevent the default action for this
|
|
// event, so let's not resize this window after all...
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If caller is not chrome and the user has not explicitly exempted the site,
|
|
* prevent window.resizeBy() by exiting early
|
|
*/
|
|
|
|
if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
if (!treeOwnerAsWin) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
int32_t width, height;
|
|
aError = treeOwnerAsWin->GetSize(&width, &height);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
// To do this correctly we have to convert what we got from GetSize
|
|
// into CSS pixels, add the arguments, do the security check, and
|
|
// then convert back to device pixels for the call to SetSize.
|
|
|
|
nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
|
|
|
|
cssSize.width += aWidthDif;
|
|
cssSize.height += aHeightDif;
|
|
|
|
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
|
|
|
|
nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
|
|
|
|
aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true);
|
|
|
|
CheckForDPIChange();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SizeToContentOuter(CallerType aCallerType,
|
|
ErrorResult& aError) {
|
|
if (!mDocShell) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If caller is not chrome and the user has not explicitly exempted the site,
|
|
* prevent window.sizeToContent() by exiting early
|
|
*/
|
|
|
|
if (!CanMoveResizeWindows(aCallerType) || IsFrame()) {
|
|
return;
|
|
}
|
|
|
|
// The content viewer does a check to make sure that it's a content
|
|
// viewer for a toplevel docshell.
|
|
nsCOMPtr<nsIContentViewer> cv;
|
|
mDocShell->GetContentViewer(getter_AddRefs(cv));
|
|
if (!cv) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
int32_t width, height;
|
|
aError = cv->GetContentSize(&width, &height);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
// Make sure the new size is following the CheckSecurityWidthAndHeight
|
|
// rules.
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
|
|
if (!treeOwner) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
|
|
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
|
|
|
|
nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = mDocShell;
|
|
aError =
|
|
treeOwner->SizeShellTo(docShell, newDevSize.width, newDevSize.height);
|
|
}
|
|
|
|
already_AddRefed<nsPIWindowRoot> nsGlobalWindowOuter::GetTopWindowRoot() {
|
|
nsPIDOMWindowOuter* piWin = GetPrivateRoot();
|
|
if (!piWin) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIWindowRoot> window =
|
|
do_QueryInterface(piWin->GetChromeEventHandler());
|
|
return window.forget();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::FirePopupBlockedEvent(
|
|
Document* aDoc, nsIURI* aPopupURI, const nsAString& aPopupWindowName,
|
|
const nsAString& aPopupWindowFeatures) {
|
|
MOZ_ASSERT(aDoc);
|
|
|
|
// Fire a "DOMPopupBlocked" event so that the UI can hear about
|
|
// blocked popups.
|
|
PopupBlockedEventInit init;
|
|
init.mBubbles = true;
|
|
init.mCancelable = true;
|
|
// XXX: This is a different object, but webidl requires an inner window here
|
|
// now.
|
|
init.mRequestingWindow = GetCurrentInnerWindowInternal();
|
|
init.mPopupWindowURI = aPopupURI;
|
|
init.mPopupWindowName = aPopupWindowName;
|
|
init.mPopupWindowFeatures = aPopupWindowFeatures;
|
|
|
|
RefPtr<PopupBlockedEvent> event = PopupBlockedEvent::Constructor(
|
|
aDoc, NS_LITERAL_STRING("DOMPopupBlocked"), init);
|
|
|
|
event->SetTrusted(true);
|
|
|
|
aDoc->DispatchEvent(*event);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::NotifyContentBlockingEvent(
|
|
unsigned aEvent, nsIChannel* aChannel, bool aBlocked, nsIURI* aURIHint,
|
|
nsIChannel* aTrackingChannel,
|
|
const mozilla::Maybe<AntiTrackingCommon::StorageAccessGrantedReason>&
|
|
aReason) {
|
|
MOZ_ASSERT(aURIHint);
|
|
DebugOnly<bool> isCookiesBlockedTracker =
|
|
aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
|
|
MOZ_ASSERT_IF(aBlocked, aReason.isNothing());
|
|
MOZ_ASSERT_IF(!isCookiesBlockedTracker, aReason.isNothing());
|
|
MOZ_ASSERT_IF(isCookiesBlockedTracker && !aBlocked, aReason.isSome());
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = GetDocShell();
|
|
if (!docShell) {
|
|
return;
|
|
}
|
|
nsCOMPtr<Document> doc = docShell->GetDocument();
|
|
NS_ENSURE_TRUE_VOID(doc);
|
|
|
|
nsCOMPtr<nsIURI> uri(aURIHint);
|
|
nsCOMPtr<nsIChannel> channel(aChannel);
|
|
nsCOMPtr<nsIClassifiedChannel> trackingChannel =
|
|
do_QueryInterface(aTrackingChannel);
|
|
|
|
static bool prefInitialized = false;
|
|
if (!prefInitialized) {
|
|
Preferences::AddBoolVarCache(
|
|
&gSyncContentBlockingNotifications,
|
|
"dom.testing.sync-content-blocking-notifications", false);
|
|
prefInitialized = true;
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> func = NS_NewRunnableFunction(
|
|
"NotifyContentBlockingEventDelayed",
|
|
[doc, docShell, uri, channel, aEvent, aBlocked, aReason,
|
|
trackingChannel]() {
|
|
// This event might come after the user has navigated to another
|
|
// page. To prevent showing the TrackingProtection UI on the wrong
|
|
// page, we need to check that the loading URI for the channel is
|
|
// the same as the URI currently loaded in the document.
|
|
if (!SameLoadingURI(doc, channel) &&
|
|
aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
|
|
return;
|
|
}
|
|
|
|
// Notify nsIWebProgressListeners of this content blocking event.
|
|
// Can be used to change the UI state.
|
|
uint32_t event = 0;
|
|
nsCOMPtr<nsISecureBrowserUI> securityUI;
|
|
docShell->GetSecurityUI(getter_AddRefs(securityUI));
|
|
if (!securityUI) {
|
|
return;
|
|
}
|
|
securityUI->GetContentBlockingEvent(&event);
|
|
nsAutoCString origin;
|
|
nsContentUtils::GetASCIIOrigin(uri, origin);
|
|
|
|
bool blockedValue = aBlocked;
|
|
bool unblocked = false;
|
|
if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
|
|
doc->SetHasTrackingContentBlocked(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasTrackingContentBlocked();
|
|
}
|
|
} else if (aEvent ==
|
|
nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
|
|
doc->SetHasTrackingContentLoaded(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasTrackingContentLoaded();
|
|
}
|
|
} else if (aEvent == nsIWebProgressListener::
|
|
STATE_BLOCKED_FINGERPRINTING_CONTENT) {
|
|
doc->SetHasFingerprintingContentBlocked(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasFingerprintingContentBlocked();
|
|
}
|
|
} else if (aEvent == nsIWebProgressListener::
|
|
STATE_LOADED_FINGERPRINTING_CONTENT) {
|
|
doc->SetHasFingerprintingContentLoaded(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasFingerprintingContentLoaded();
|
|
}
|
|
} else if (aEvent ==
|
|
nsIWebProgressListener::STATE_BLOCKED_CRYPTOMINING_CONTENT) {
|
|
doc->SetHasCryptominingContentBlocked(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasCryptominingContentBlocked();
|
|
}
|
|
} else if (aEvent ==
|
|
nsIWebProgressListener::STATE_LOADED_CRYPTOMINING_CONTENT) {
|
|
doc->SetHasCryptominingContentLoaded(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasCryptominingContentLoaded();
|
|
}
|
|
} else if (aEvent == nsIWebProgressListener::
|
|
STATE_BLOCKED_SOCIALTRACKING_CONTENT) {
|
|
doc->SetHasSocialTrackingContentBlocked(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasSocialTrackingContentBlocked();
|
|
}
|
|
} else if (aEvent == nsIWebProgressListener::
|
|
STATE_LOADED_SOCIALTRACKING_CONTENT) {
|
|
doc->SetHasSocialTrackingContentLoaded(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasSocialTrackingContentLoaded();
|
|
}
|
|
} else if (aEvent == nsIWebProgressListener::
|
|
STATE_COOKIES_BLOCKED_BY_PERMISSION) {
|
|
doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasCookiesBlockedByPermission();
|
|
}
|
|
} else if (aEvent ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
|
|
nsTArray<nsCString> trackingFullHashes;
|
|
if (trackingChannel) {
|
|
Unused << trackingChannel->GetMatchedTrackingFullHashes(
|
|
trackingFullHashes);
|
|
}
|
|
doc->SetHasTrackingCookiesBlocked(aBlocked, origin, aReason,
|
|
trackingFullHashes);
|
|
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasTrackingCookiesBlocked();
|
|
}
|
|
} else if (aEvent ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
|
|
doc->SetHasAllCookiesBlocked(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasAllCookiesBlocked();
|
|
}
|
|
} else if (aEvent ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
|
|
doc->SetHasForeignCookiesBlocked(aBlocked, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasForeignCookiesBlocked();
|
|
}
|
|
} else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
|
|
MOZ_ASSERT(!aBlocked,
|
|
"We don't expected to see blocked STATE_COOKIES_LOADED");
|
|
// Note that the logic in this branch is the logical negation of
|
|
// the logic in other branches, since the Document API we have is
|
|
// phrased in "loaded" terms as opposed to "blocked" terms.
|
|
blockedValue = !aBlocked;
|
|
doc->SetHasCookiesLoaded(blockedValue, origin);
|
|
if (!aBlocked) {
|
|
unblocked = !doc->GetHasCookiesLoaded();
|
|
}
|
|
} else {
|
|
// Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
|
|
}
|
|
const uint32_t oldEvent = event;
|
|
if (blockedValue) {
|
|
event |= aEvent;
|
|
} else if (unblocked) {
|
|
event &= ~aEvent;
|
|
}
|
|
|
|
if (event == oldEvent
|
|
#ifdef ANDROID
|
|
// GeckoView always needs to notify about blocked trackers,
|
|
// since the GeckoView API always needs to report the URI and
|
|
// type of any blocked tracker. We use a platform-dependent code
|
|
// path here because reporting this notification on desktop
|
|
// platforms isn't necessary and doing so can have a big
|
|
// performance cost.
|
|
&& aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
|
|
#endif
|
|
) {
|
|
// Avoid dispatching repeated notifications when nothing has
|
|
// changed
|
|
return;
|
|
}
|
|
|
|
nsDocShell::Cast(docShell)->nsDocLoader::OnContentBlockingEvent(channel,
|
|
event);
|
|
});
|
|
nsresult rv;
|
|
if (gSyncContentBlockingNotifications) {
|
|
rv = func->Run();
|
|
} else {
|
|
rv = NS_DispatchToCurrentThreadQueue(func.forget(), 100,
|
|
EventQueuePriority::Idle);
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// static
|
|
bool nsGlobalWindowOuter::SameLoadingURI(Document* aDoc, nsIChannel* aChannel) {
|
|
nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
|
|
if (!docURI) {
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->LoadInfo();
|
|
nsCOMPtr<nsIPrincipal> channelLoadingPrincipal =
|
|
channelLoadInfo->LoadingPrincipal();
|
|
if (!channelLoadingPrincipal) {
|
|
// TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top
|
|
// level loads should not be blocked by Tracking Protection, so we will
|
|
// return false
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsIURI> channelLoadingURI;
|
|
channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI));
|
|
if (!channelLoadingURI) {
|
|
return false;
|
|
}
|
|
bool equals = false;
|
|
nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals);
|
|
return NS_SUCCEEDED(rv) && equals;
|
|
}
|
|
|
|
// static
|
|
bool nsGlobalWindowOuter::CanSetProperty(const char* aPrefName) {
|
|
// Chrome can set any property.
|
|
if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
|
|
return true;
|
|
}
|
|
|
|
// If the pref is set to true, we can not set the property
|
|
// and vice versa.
|
|
return !Preferences::GetBool(aPrefName, true);
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::PopupWhitelisted() {
|
|
if (mDoc && PopupBlocker::CanShowPopupByPermission(mDoc->NodePrincipal())) {
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
|
|
if (parent == this) {
|
|
return false;
|
|
}
|
|
|
|
return nsGlobalWindowOuter::Cast(parent)->PopupWhitelisted();
|
|
}
|
|
|
|
/*
|
|
* Examine the current document state to see if we're in a way that is
|
|
* typically abused by web designers. The window.open code uses this
|
|
* routine to determine whether to allow the new window.
|
|
* Returns a value from the PopupControlState enum.
|
|
*/
|
|
PopupBlocker::PopupControlState nsGlobalWindowOuter::RevisePopupAbuseLevel(
|
|
PopupBlocker::PopupControlState aControl) {
|
|
NS_ASSERTION(mDocShell, "Must have docshell");
|
|
|
|
if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
|
|
return PopupBlocker::openAllowed;
|
|
}
|
|
|
|
PopupBlocker::PopupControlState abuse = aControl;
|
|
switch (abuse) {
|
|
case PopupBlocker::openControlled:
|
|
case PopupBlocker::openBlocked:
|
|
case PopupBlocker::openOverridden:
|
|
if (PopupWhitelisted())
|
|
abuse = PopupBlocker::PopupControlState(abuse - 1);
|
|
break;
|
|
case PopupBlocker::openAbused:
|
|
if (PopupWhitelisted())
|
|
// Skip PopupBlocker::openBlocked
|
|
abuse = PopupBlocker::openControlled;
|
|
break;
|
|
case PopupBlocker::openAllowed:
|
|
break;
|
|
default:
|
|
NS_WARNING("Strange PopupControlState!");
|
|
}
|
|
|
|
// limit the number of simultaneously open popups
|
|
if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked ||
|
|
abuse == PopupBlocker::openControlled) {
|
|
int32_t popupMax = Preferences::GetInt("dom.popup_maximum", -1);
|
|
if (popupMax >= 0 && gOpenPopupSpamCount >= popupMax)
|
|
abuse = PopupBlocker::openOverridden;
|
|
}
|
|
|
|
// If this popup is allowed, let's block any other for this event, forcing
|
|
// PopupBlocker::openBlocked state.
|
|
if ((abuse == PopupBlocker::openAllowed ||
|
|
abuse == PopupBlocker::openControlled) &&
|
|
StaticPrefs::dom_block_multiple_popups() && !PopupWhitelisted() &&
|
|
!PopupBlocker::TryUsePopupOpeningToken(mDoc->NodePrincipal())) {
|
|
abuse = PopupBlocker::openBlocked;
|
|
}
|
|
|
|
return abuse;
|
|
}
|
|
|
|
/* If a window open is blocked, fire the appropriate DOM events. */
|
|
void nsGlobalWindowOuter::FireAbuseEvents(
|
|
const nsAString& aPopupURL, const nsAString& aPopupWindowName,
|
|
const nsAString& aPopupWindowFeatures) {
|
|
// fetch the URI of the window requesting the opened window
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> window = GetTop();
|
|
if (!window) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<Document> topDoc = window->GetDoc();
|
|
nsCOMPtr<nsIURI> popupURI;
|
|
|
|
// build the URI of the would-have-been popup window
|
|
// (see nsWindowWatcher::URIfromURL)
|
|
|
|
// first, fetch the opener's base URI
|
|
|
|
nsIURI* baseURL = nullptr;
|
|
|
|
nsCOMPtr<Document> doc = GetEntryDocument();
|
|
if (doc) baseURL = doc->GetDocBaseURI();
|
|
|
|
// use the base URI to build what would have been the popup's URI
|
|
nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
|
|
if (ios)
|
|
ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), nullptr, baseURL,
|
|
getter_AddRefs(popupURI));
|
|
|
|
// fire an event block full of informative URIs
|
|
FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
|
|
aPopupWindowFeatures);
|
|
}
|
|
|
|
Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenOuter(
|
|
const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
|
|
ErrorResult& aError) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> window;
|
|
aError = OpenJS(aUrl, aName, aOptions, getter_AddRefs(window));
|
|
RefPtr<BrowsingContext> bc;
|
|
if (!window || !(bc = window->GetBrowsingContext())) {
|
|
return nullptr;
|
|
}
|
|
return WindowProxyHolder(bc.forget());
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::Open(const nsAString& aUrl,
|
|
const nsAString& aName,
|
|
const nsAString& aOptions,
|
|
nsDocShellLoadState* aLoadState,
|
|
bool aForceNoOpener,
|
|
nsPIDOMWindowOuter** _retval) {
|
|
return OpenInternal(aUrl, aName, aOptions,
|
|
false, // aDialog
|
|
false, // aContentModal
|
|
true, // aCalledNoScript
|
|
false, // aDoJSFixups
|
|
true, // aNavigate
|
|
nullptr, nullptr, // No args
|
|
aLoadState, aForceNoOpener, _retval);
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::OpenJS(const nsAString& aUrl,
|
|
const nsAString& aName,
|
|
const nsAString& aOptions,
|
|
nsPIDOMWindowOuter** _retval) {
|
|
return OpenInternal(aUrl, aName, aOptions,
|
|
false, // aDialog
|
|
false, // aContentModal
|
|
false, // aCalledNoScript
|
|
true, // aDoJSFixups
|
|
true, // aNavigate
|
|
nullptr, nullptr, // No args
|
|
nullptr, // aLoadState
|
|
false, // aForceNoOpener
|
|
_retval);
|
|
}
|
|
|
|
// like Open, but attaches to the new window any extra parameters past
|
|
// [features] as a JS property named "arguments"
|
|
nsresult nsGlobalWindowOuter::OpenDialog(const nsAString& aUrl,
|
|
const nsAString& aName,
|
|
const nsAString& aOptions,
|
|
nsISupports* aExtraArgument,
|
|
nsPIDOMWindowOuter** _retval) {
|
|
return OpenInternal(aUrl, aName, aOptions,
|
|
true, // aDialog
|
|
false, // aContentModal
|
|
true, // aCalledNoScript
|
|
false, // aDoJSFixups
|
|
true, // aNavigate
|
|
nullptr, aExtraArgument, // Arguments
|
|
nullptr, // aLoadState
|
|
false, // aForceNoOpener
|
|
_retval);
|
|
}
|
|
|
|
// Like Open, but passes aNavigate=false.
|
|
/* virtual */
|
|
nsresult nsGlobalWindowOuter::OpenNoNavigate(const nsAString& aUrl,
|
|
const nsAString& aName,
|
|
const nsAString& aOptions,
|
|
nsPIDOMWindowOuter** _retval) {
|
|
return OpenInternal(aUrl, aName, aOptions,
|
|
false, // aDialog
|
|
false, // aContentModal
|
|
true, // aCalledNoScript
|
|
false, // aDoJSFixups
|
|
false, // aNavigate
|
|
nullptr, nullptr, // No args
|
|
nullptr, // aLoadState
|
|
false, // aForceNoOpener
|
|
_retval);
|
|
}
|
|
|
|
Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenDialogOuter(
|
|
JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
|
|
const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
|
|
ErrorResult& aError) {
|
|
nsCOMPtr<nsIJSArgArray> argvArray;
|
|
aError =
|
|
NS_CreateJSArgv(aCx, aExtraArgument.Length(), aExtraArgument.Elements(),
|
|
getter_AddRefs(argvArray));
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> dialog;
|
|
aError = OpenInternal(aUrl, aName, aOptions,
|
|
true, // aDialog
|
|
false, // aContentModal
|
|
false, // aCalledNoScript
|
|
false, // aDoJSFixups
|
|
true, // aNavigate
|
|
argvArray, nullptr, // Arguments
|
|
nullptr, // aLoadState
|
|
false, // aForceNoOpener
|
|
getter_AddRefs(dialog));
|
|
RefPtr<BrowsingContext> bc;
|
|
if (!dialog || !(bc = dialog->GetBrowsingContext())) {
|
|
return nullptr;
|
|
}
|
|
return WindowProxyHolder(bc.forget());
|
|
}
|
|
|
|
BrowsingContext* nsGlobalWindowOuter::GetFramesOuter() {
|
|
RefPtr<nsPIDOMWindowOuter> frames(this);
|
|
FlushPendingNotifications(FlushType::ContentAndNotify);
|
|
return mBrowsingContext;
|
|
}
|
|
|
|
/* static */
|
|
nsGlobalWindowInner* nsGlobalWindowOuter::CallerInnerWindow(JSContext* aCx) {
|
|
nsIGlobalObject* global = GetIncumbentGlobal();
|
|
NS_ENSURE_TRUE(global, nullptr);
|
|
JS::Rooted<JSObject*> scope(aCx, global->GetGlobalJSObject());
|
|
NS_ENSURE_TRUE(scope, nullptr);
|
|
|
|
// When Jetpack runs content scripts inside a sandbox, it uses
|
|
// sandboxPrototype to make them appear as though they're running in the
|
|
// scope of the page. So when a content script invokes postMessage, it expects
|
|
// the |source| of the received message to be the window set as the
|
|
// sandboxPrototype. This used to work incidentally for unrelated reasons, but
|
|
// now we need to do some special handling to support it.
|
|
if (xpc::IsSandbox(scope)) {
|
|
JSAutoRealm ar(aCx, scope);
|
|
JS::Rooted<JSObject*> scopeProto(aCx);
|
|
bool ok = JS_GetPrototype(aCx, scope, &scopeProto);
|
|
NS_ENSURE_TRUE(ok, nullptr);
|
|
if (scopeProto && xpc::IsSandboxPrototypeProxy(scopeProto) &&
|
|
// Our current Realm on aCx is the sandbox. Using that for the
|
|
// CheckedUnwrapDynamic call makes sense: if the sandbox can unwrap the
|
|
// window, we can use it. And we do want CheckedUnwrapDynamic, because
|
|
// the whole point is to unwrap windows.
|
|
(scopeProto = js::CheckedUnwrapDynamic(
|
|
scopeProto, aCx, /* stopAtWindowProxy = */ false))) {
|
|
global = xpc::NativeGlobal(scopeProto);
|
|
NS_ENSURE_TRUE(global, nullptr);
|
|
}
|
|
}
|
|
|
|
// The calling window must be holding a reference, so we can return a weak
|
|
// pointer.
|
|
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
|
|
return nsGlobalWindowInner::Cast(win);
|
|
}
|
|
|
|
/* static */
|
|
bool nsGlobalWindowOuter::GatherPostMessageData(
|
|
JSContext* aCx, const nsAString& aTargetOrigin, BrowsingContext** aSource,
|
|
nsAString& aOrigin, nsIURI** aTargetOriginURI,
|
|
nsIPrincipal** aCallerPrincipal, nsGlobalWindowInner** aCallerInnerWindow,
|
|
nsIURI** aCallerDocumentURI, ErrorResult& aError) {
|
|
//
|
|
// Window.postMessage is an intentional subversion of the same-origin policy.
|
|
// As such, this code must be particularly careful in the information it
|
|
// exposes to calling code.
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
|
|
//
|
|
|
|
// First, get the caller's window
|
|
RefPtr<nsGlobalWindowInner> callerInnerWin = CallerInnerWindow(aCx);
|
|
nsIPrincipal* callerPrin;
|
|
if (callerInnerWin) {
|
|
RefPtr<Document> doc = callerInnerWin->GetExtantDoc();
|
|
if (!doc) {
|
|
return false;
|
|
}
|
|
NS_IF_ADDREF(*aCallerDocumentURI = doc->GetDocumentURI());
|
|
|
|
// Compute the caller's origin either from its principal or, in the case the
|
|
// principal doesn't carry a URI (e.g. the system principal), the caller's
|
|
// document. We must get this now instead of when the event is created and
|
|
// dispatched, because ultimately it is the identity of the calling window
|
|
// *now* that determines who sent the message (and not an identity which
|
|
// might have changed due to intervening navigations).
|
|
callerPrin = callerInnerWin->GetPrincipal();
|
|
} else {
|
|
// In case the global is not a window, it can be a sandbox, and the
|
|
// sandbox's principal can be used for the security check.
|
|
nsIGlobalObject* global = GetIncumbentGlobal();
|
|
NS_ASSERTION(global, "Why is there no global object?");
|
|
callerPrin = global->PrincipalOrNull();
|
|
}
|
|
if (!callerPrin) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> callerOuterURI;
|
|
if (NS_FAILED(callerPrin->GetURI(getter_AddRefs(callerOuterURI)))) {
|
|
return false;
|
|
}
|
|
|
|
if (callerOuterURI) {
|
|
// if the principal has a URI, use that to generate the origin
|
|
nsContentUtils::GetUTFOrigin(callerPrin, aOrigin);
|
|
} else if (callerInnerWin) {
|
|
if (!*aCallerDocumentURI) {
|
|
return false;
|
|
}
|
|
// otherwise use the URI of the document to generate origin
|
|
nsContentUtils::GetUTFOrigin(*aCallerDocumentURI, aOrigin);
|
|
} else {
|
|
// in case of a sandbox with a system principal origin can be empty
|
|
if (!nsContentUtils::IsSystemPrincipal(callerPrin)) {
|
|
return false;
|
|
}
|
|
}
|
|
NS_IF_ADDREF(*aCallerPrincipal = callerPrin);
|
|
|
|
// "/" indicates same origin as caller, "*" indicates no specific origin is
|
|
// required.
|
|
if (!aTargetOrigin.EqualsASCII("/") && !aTargetOrigin.EqualsASCII("*")) {
|
|
nsCOMPtr<nsIURI> targetOriginURI;
|
|
if (NS_FAILED(NS_NewURI(getter_AddRefs(targetOriginURI), aTargetOrigin))) {
|
|
aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
|
return false;
|
|
}
|
|
|
|
nsresult rv = NS_MutateURI(targetOriginURI)
|
|
.SetUserPass(EmptyCString())
|
|
.SetPathQueryRef(EmptyCString())
|
|
.Finalize(aTargetOriginURI);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!nsContentUtils::IsCallerChrome() && callerInnerWin &&
|
|
callerInnerWin->GetOuterWindowInternal()) {
|
|
NS_ADDREF(*aSource = callerInnerWin->GetOuterWindowInternal()
|
|
->GetBrowsingContext());
|
|
} else {
|
|
*aSource = nullptr;
|
|
}
|
|
|
|
callerInnerWin.forget(aCallerInnerWindow);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::GetPrincipalForPostMessage(
|
|
const nsAString& aTargetOrigin, nsIURI* aTargetOriginURI,
|
|
nsIPrincipal* aCallerPrincipal, nsIPrincipal& aSubjectPrincipal,
|
|
nsIPrincipal** aProvidedPrincipal) {
|
|
//
|
|
// Window.postMessage is an intentional subversion of the same-origin policy.
|
|
// As such, this code must be particularly careful in the information it
|
|
// exposes to calling code.
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
|
|
//
|
|
|
|
// Convert the provided origin string into a URI for comparison purposes.
|
|
nsCOMPtr<nsIPrincipal> providedPrincipal;
|
|
|
|
if (aTargetOrigin.EqualsASCII("/")) {
|
|
providedPrincipal = aCallerPrincipal;
|
|
}
|
|
// "*" indicates no specific origin is required.
|
|
else if (!aTargetOrigin.EqualsASCII("*")) {
|
|
OriginAttributes attrs = aSubjectPrincipal.OriginAttributesRef();
|
|
if (aSubjectPrincipal.IsSystemPrincipal()) {
|
|
auto principal = BasePrincipal::Cast(GetPrincipal());
|
|
|
|
if (attrs != principal->OriginAttributesRef()) {
|
|
nsCOMPtr<nsIURI> targetURI;
|
|
nsAutoCString targetURL;
|
|
nsAutoCString sourceOrigin;
|
|
nsAutoCString targetOrigin;
|
|
|
|
if (NS_FAILED(principal->GetURI(getter_AddRefs(targetURI))) ||
|
|
NS_FAILED(targetURI->GetAsciiSpec(targetURL)) ||
|
|
NS_FAILED(principal->GetOrigin(targetOrigin)) ||
|
|
NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
|
|
NS_WARNING("Failed to get source and target origins");
|
|
return false;
|
|
}
|
|
|
|
nsContentUtils::LogSimpleConsoleError(
|
|
NS_ConvertUTF8toUTF16(nsPrintfCString(
|
|
R"(Attempting to post a message to window with url "%s" and )"
|
|
R"(origin "%s" from a system principal scope with mismatched )"
|
|
R"(origin "%s".)",
|
|
targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
|
|
"DOM", !!principal->PrivateBrowsingId(),
|
|
nsContentUtils::IsSystemPrincipal(principal));
|
|
|
|
attrs = principal->OriginAttributesRef();
|
|
}
|
|
}
|
|
|
|
// Create a nsIPrincipal inheriting the app/browser attributes from the
|
|
// caller.
|
|
providedPrincipal =
|
|
BasePrincipal::CreateContentPrincipal(aTargetOriginURI, attrs);
|
|
if (NS_WARN_IF(!providedPrincipal)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// We still need to check the originAttributes if the target origin is '*'.
|
|
// But we will ingore the FPD here since the FPDs are possible to be
|
|
// different.
|
|
auto principal = BasePrincipal::Cast(GetPrincipal());
|
|
NS_ENSURE_TRUE(principal, false);
|
|
|
|
OriginAttributes targetAttrs = principal->OriginAttributesRef();
|
|
OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
|
|
// We have to exempt the check of OA if the subject prioncipal is a system
|
|
// principal since there are many tests try to post messages to content from
|
|
// chrome with a mismatch OA. For example, using the ContentTask.spawn() to
|
|
// post a message into a private browsing window. The injected code in
|
|
// ContentTask.spawn() will be executed under the system principal and the
|
|
// OA of the system principal mismatches with the OA of a private browsing
|
|
// window.
|
|
MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.IsSystemPrincipal() ||
|
|
sourceAttrs.EqualsIgnoringFPD(targetAttrs));
|
|
|
|
// If 'privacy.firstparty.isolate.block_post_message' is true, we will block
|
|
// postMessage across different first party domains.
|
|
if (OriginAttributes::IsBlockPostMessageForFPI() &&
|
|
!aSubjectPrincipal.IsSystemPrincipal() &&
|
|
sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
providedPrincipal.forget(aProvidedPrincipal);
|
|
return true;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
|
|
JS::Handle<JS::Value> aMessage,
|
|
const nsAString& aTargetOrigin,
|
|
JS::Handle<JS::Value> aTransfer,
|
|
nsIPrincipal& aSubjectPrincipal,
|
|
ErrorResult& aError) {
|
|
RefPtr<BrowsingContext> sourceBc;
|
|
nsAutoString origin;
|
|
nsCOMPtr<nsIURI> targetOriginURI;
|
|
nsCOMPtr<nsIPrincipal> callerPrincipal;
|
|
RefPtr<nsGlobalWindowInner> callerInnerWindow;
|
|
nsCOMPtr<nsIURI> callerDocumentURI;
|
|
if (!GatherPostMessageData(aCx, aTargetOrigin, getter_AddRefs(sourceBc),
|
|
origin, getter_AddRefs(targetOriginURI),
|
|
getter_AddRefs(callerPrincipal),
|
|
getter_AddRefs(callerInnerWindow),
|
|
getter_AddRefs(callerDocumentURI), aError)) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> providedPrincipal;
|
|
if (!GetPrincipalForPostMessage(aTargetOrigin, targetOriginURI,
|
|
callerPrincipal, aSubjectPrincipal,
|
|
getter_AddRefs(providedPrincipal))) {
|
|
return;
|
|
}
|
|
|
|
// Create and asynchronously dispatch a runnable which will handle actual DOM
|
|
// event creation and dispatch.
|
|
RefPtr<PostMessageEvent> event = new PostMessageEvent(
|
|
sourceBc, origin, this, providedPrincipal,
|
|
callerInnerWindow ? callerInnerWindow->WindowID() : 0, callerDocumentURI);
|
|
|
|
event->Write(aCx, aMessage, aTransfer, aError);
|
|
if (NS_WARN_IF(aError.Failed())) {
|
|
return;
|
|
}
|
|
|
|
if (mDoc &&
|
|
StaticPrefs::dom_separate_event_queue_for_post_message_enabled() &&
|
|
!DocGroup::TryToLoadIframesInBackground()) {
|
|
Document* doc = mDoc->GetTopLevelContentDocument();
|
|
if (doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE) {
|
|
// As long as the top level is loading, we can dispatch events to the
|
|
// queue because the queue will be flushed eventually
|
|
mozilla::dom::TabGroup* tabGroup = TabGroup();
|
|
aError = tabGroup->QueuePostMessageEvent(event.forget());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mDoc && DocGroup::TryToLoadIframesInBackground()) {
|
|
RefPtr<nsIDocShell> docShell = GetDocShell();
|
|
RefPtr<nsDocShell> dShell = nsDocShell::Cast(docShell);
|
|
|
|
// PostMessage that are added to the tabGroup are the ones that
|
|
// can be flushed when the top level document is loaded
|
|
if (dShell) {
|
|
if (!dShell->TreatAsBackgroundLoad()) {
|
|
Document* doc = mDoc->GetTopLevelContentDocument();
|
|
if (doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE) {
|
|
// As long as the top level is loading, we can dispatch events to the
|
|
// queue because the queue will be flushed eventually
|
|
mozilla::dom::TabGroup* tabGroup = TabGroup();
|
|
aError = tabGroup->QueuePostMessageEvent(event.forget());
|
|
return;
|
|
}
|
|
} else if (mDoc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE) {
|
|
mozilla::dom::DocGroup* docGroup = GetDocGroup();
|
|
aError = docGroup->QueueIframePostMessages(event.forget(),
|
|
dShell->GetOuterWindowID());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
aError = Dispatch(TaskCategory::Other, event.forget());
|
|
}
|
|
|
|
class nsCloseEvent : public Runnable {
|
|
RefPtr<nsGlobalWindowOuter> mWindow;
|
|
bool mIndirect;
|
|
|
|
nsCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect)
|
|
: mozilla::Runnable("nsCloseEvent"),
|
|
mWindow(aWindow),
|
|
mIndirect(aIndirect) {}
|
|
|
|
public:
|
|
static nsresult PostCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect) {
|
|
nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow, aIndirect);
|
|
nsresult rv = aWindow->Dispatch(TaskCategory::Other, ev.forget());
|
|
if (NS_SUCCEEDED(rv)) aWindow->MaybeForgiveSpamCount();
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHOD Run() override {
|
|
if (mWindow) {
|
|
if (mIndirect) {
|
|
return PostCloseEvent(mWindow, false);
|
|
}
|
|
mWindow->ReallyCloseWindow();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
bool nsGlobalWindowOuter::CanClose() {
|
|
if (mIsChrome) {
|
|
nsCOMPtr<nsIBrowserDOMWindow> bwin;
|
|
GetBrowserDOMWindow(getter_AddRefs(bwin));
|
|
|
|
bool canClose = true;
|
|
if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
|
|
return canClose;
|
|
}
|
|
}
|
|
|
|
if (!mDocShell) {
|
|
return true;
|
|
}
|
|
|
|
// Ask the content viewer whether the toplevel window can close.
|
|
// If the content viewer returns false, it is responsible for calling
|
|
// Close() as soon as it is possible for the window to close.
|
|
// This allows us to not close the window while printing is happening.
|
|
|
|
nsCOMPtr<nsIContentViewer> cv;
|
|
mDocShell->GetContentViewer(getter_AddRefs(cv));
|
|
if (cv) {
|
|
bool canClose;
|
|
nsresult rv = cv->PermitUnload(&canClose);
|
|
if (NS_SUCCEEDED(rv) && !canClose) return false;
|
|
|
|
rv = cv->RequestWindowClose(&canClose);
|
|
if (NS_SUCCEEDED(rv) && !canClose) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) {
|
|
if (!mDocShell || IsInModalState() ||
|
|
(IsFrame() && !mDocShell->GetIsMozBrowser())) {
|
|
// window.close() is called on a frame in a frameset, on a window
|
|
// that's already closed, or on a window for which there's
|
|
// currently a modal dialog open. Ignore such calls.
|
|
return;
|
|
}
|
|
|
|
if (mHavePendingClose) {
|
|
// We're going to be closed anyway; do nothing since we don't want
|
|
// to double-close
|
|
return;
|
|
}
|
|
|
|
if (mBlockScriptedClosingFlag) {
|
|
// A script's popup has been blocked and we don't want
|
|
// the window to be closed directly after this event,
|
|
// so the user can see that there was a blocked popup.
|
|
return;
|
|
}
|
|
|
|
// Don't allow scripts from content to close non-neterror windows that
|
|
// were not opened by script.
|
|
if (mDoc) {
|
|
nsAutoString url;
|
|
nsresult rv = mDoc->GetURL(url);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
if (!StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) &&
|
|
!mHadOriginalOpener && !aTrustedCaller) {
|
|
bool allowClose =
|
|
mAllowScriptsToClose ||
|
|
Preferences::GetBool("dom.allow_scripts_to_close_windows", true);
|
|
if (!allowClose) {
|
|
// We're blocking the close operation
|
|
// report localized error msg in JS console
|
|
nsContentUtils::ReportToConsole(
|
|
nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM Window"),
|
|
mDoc, // Better name for the category?
|
|
nsContentUtils::eDOM_PROPERTIES, "WindowCloseBlockedWarning");
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mInClose && !mIsClosed && !CanClose()) {
|
|
return;
|
|
}
|
|
|
|
// Fire a DOM event notifying listeners that this window is about to
|
|
// be closed. The tab UI code may choose to cancel the default
|
|
// action for this event, if so, we won't actually close the window
|
|
// (since the tab UI code will close the tab in stead). Sure, this
|
|
// could be abused by content code, but do we care? I don't think
|
|
// so...
|
|
|
|
bool wasInClose = mInClose;
|
|
mInClose = true;
|
|
|
|
if (!DispatchCustomEvent(NS_LITERAL_STRING("DOMWindowClose"))) {
|
|
// Someone chose to prevent the default action for this event, if
|
|
// so, let's not close this window after all...
|
|
|
|
mInClose = wasInClose;
|
|
return;
|
|
}
|
|
|
|
FinalClose();
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::Close() {
|
|
CloseOuter(/* aTrustedCaller = */ true);
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ForceClose() {
|
|
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
|
|
|
|
if (IsFrame() || !mDocShell) {
|
|
// This may be a frame in a frameset, or a window that's already closed.
|
|
// Ignore such calls.
|
|
return;
|
|
}
|
|
|
|
if (mHavePendingClose) {
|
|
// We're going to be closed anyway; do nothing since we don't want
|
|
// to double-close
|
|
return;
|
|
}
|
|
|
|
mInClose = true;
|
|
|
|
DispatchCustomEvent(NS_LITERAL_STRING("DOMWindowClose"));
|
|
|
|
FinalClose();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::FinalClose() {
|
|
// Flag that we were closed.
|
|
mIsClosed = true;
|
|
|
|
// If we get here from CloseOuter then it means that the parent process is
|
|
// going to close our window for us. It's just important to set mIsClosed.
|
|
if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
|
return;
|
|
}
|
|
|
|
// This stuff is non-sensical but incredibly fragile. The reasons for the
|
|
// behavior here don't make sense today and may not have ever made sense,
|
|
// but various bits of frontend code break when you change them. If you need
|
|
// to fix up this behavior, feel free to. It's a righteous task, but involves
|
|
// wrestling with various download manager tests, frontend code, and possible
|
|
// broken addons. The chrome tests in toolkit/mozapps/downloads are a good
|
|
// testing ground.
|
|
//
|
|
// In particular, if some inner of |win| is the entry global, we must
|
|
// complete _two_ round-trips to the event loop before the call to
|
|
// ReallyCloseWindow. This allows setTimeout handlers that are set after
|
|
// FinalClose() is called to run before the window is torn down.
|
|
nsCOMPtr<nsPIDOMWindowInner> entryWindow =
|
|
do_QueryInterface(GetEntryGlobal());
|
|
bool indirect = entryWindow && entryWindow->GetOuterWindow() == this;
|
|
if (NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) {
|
|
ReallyCloseWindow();
|
|
} else {
|
|
mHavePendingClose = true;
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ReallyCloseWindow() {
|
|
// Make sure we never reenter this method.
|
|
mHavePendingClose = true;
|
|
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
|
|
|
// If there's no treeOwnerAsWin, this window must already be closed.
|
|
|
|
if (treeOwnerAsWin) {
|
|
// but if we're a browser window we could be in some nasty
|
|
// self-destroying cascade that we should mostly ignore
|
|
|
|
if (mDocShell) {
|
|
nsCOMPtr<nsIBrowserDOMWindow> bwin;
|
|
nsCOMPtr<nsIDocShellTreeItem> rootItem;
|
|
mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
|
|
nsCOMPtr<nsPIDOMWindowOuter> rootWin =
|
|
rootItem ? rootItem->GetWindow() : nullptr;
|
|
nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(rootWin));
|
|
if (chromeWin) chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
|
|
|
|
if (rootWin) {
|
|
/* Normally we destroy the entire window, but not if
|
|
this DOM window belongs to a tabbed browser and doesn't
|
|
correspond to a tab. This allows a well-behaved tab
|
|
to destroy the container as it should but is a final measure
|
|
to prevent an errant tab from doing so when it shouldn't.
|
|
This works because we reach this code when we shouldn't only
|
|
in the particular circumstance that we belong to a tab
|
|
that has just been closed (and is therefore already missing
|
|
from the list of browsers) (and has an unload handler
|
|
that closes the window). */
|
|
// XXXbz now that we have mHavePendingClose, is this needed?
|
|
bool isTab;
|
|
if (rootWin == this || !bwin ||
|
|
(NS_SUCCEEDED(bwin->IsTabContentWindow(this, &isTab)) && isTab)) {
|
|
treeOwnerAsWin->Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
CleanUp();
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::EnterModalState() {
|
|
// GetScriptableTop, not GetTop, so that EnterModalState works properly with
|
|
// <iframe mozbrowser>.
|
|
nsGlobalWindowOuter* topWin = GetScriptableTopInternal();
|
|
|
|
if (!topWin) {
|
|
NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
|
|
return;
|
|
}
|
|
|
|
// If there is an active ESM in this window, clear it. Otherwise, this can
|
|
// cause a problem if a modal state is entered during a mouseup event.
|
|
EventStateManager* activeESM = static_cast<EventStateManager*>(
|
|
EventStateManager::GetActiveEventStateManager());
|
|
if (activeESM && activeESM->GetPresContext()) {
|
|
PresShell* activePresShell = activeESM->GetPresContext()->GetPresShell();
|
|
if (activePresShell && (nsContentUtils::ContentIsCrossDocDescendantOf(
|
|
activePresShell->GetDocument(), mDoc) ||
|
|
nsContentUtils::ContentIsCrossDocDescendantOf(
|
|
mDoc, activePresShell->GetDocument()))) {
|
|
EventStateManager::ClearGlobalActiveContent(activeESM);
|
|
|
|
PresShell::ReleaseCapturingContent();
|
|
|
|
if (activePresShell) {
|
|
RefPtr<nsFrameSelection> frameSelection =
|
|
activePresShell->FrameSelection();
|
|
frameSelection->SetDragState(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there are any drag and drop operations in flight, try to end them.
|
|
nsCOMPtr<nsIDragService> ds =
|
|
do_GetService("@mozilla.org/widget/dragservice;1");
|
|
if (ds) {
|
|
ds->EndDragSession(true, 0);
|
|
}
|
|
|
|
// Clear the capturing content if it is under topDoc.
|
|
// Usually the activeESM check above does that, but there are cases when
|
|
// we don't have activeESM, or it is for different document.
|
|
Document* topDoc = topWin->GetExtantDoc();
|
|
nsIContent* capturingContent = PresShell::GetCapturingContent();
|
|
if (capturingContent && topDoc &&
|
|
nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
|
|
PresShell::ReleaseCapturingContent();
|
|
}
|
|
|
|
if (topWin->mModalStateDepth == 0) {
|
|
NS_ASSERTION(!topWin->mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
|
|
|
|
topWin->mSuspendedDoc = topDoc;
|
|
if (topDoc) {
|
|
topDoc->SuppressEventHandling();
|
|
}
|
|
|
|
nsGlobalWindowInner* inner = topWin->GetCurrentInnerWindowInternal();
|
|
if (inner) {
|
|
topWin->GetCurrentInnerWindowInternal()->Suspend();
|
|
}
|
|
}
|
|
topWin->mModalStateDepth++;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::LeaveModalState() {
|
|
nsGlobalWindowOuter* topWin = GetScriptableTopInternal();
|
|
|
|
if (!topWin) {
|
|
NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?");
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(topWin->mModalStateDepth != 0);
|
|
MOZ_ASSERT(IsSuspended());
|
|
MOZ_ASSERT(topWin->IsSuspended());
|
|
topWin->mModalStateDepth--;
|
|
|
|
nsGlobalWindowInner* inner = topWin->GetCurrentInnerWindowInternal();
|
|
|
|
if (topWin->mModalStateDepth == 0) {
|
|
if (inner) {
|
|
inner->Resume();
|
|
}
|
|
|
|
if (topWin->mSuspendedDoc) {
|
|
nsCOMPtr<Document> currentDoc = topWin->GetExtantDoc();
|
|
topWin->mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(
|
|
currentDoc == topWin->mSuspendedDoc);
|
|
topWin->mSuspendedDoc = nullptr;
|
|
}
|
|
}
|
|
|
|
// Remember the time of the last dialog quit.
|
|
if (inner) {
|
|
inner->mLastDialogQuitTime = TimeStamp::Now();
|
|
}
|
|
|
|
if (topWin->mModalStateDepth == 0) {
|
|
RefPtr<Event> event = NS_NewDOMEvent(inner, nullptr, nullptr);
|
|
event->InitEvent(NS_LITERAL_STRING("endmodalstate"), true, false);
|
|
event->SetTrusted(true);
|
|
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
|
|
topWin->DispatchEvent(*event);
|
|
}
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::IsInModalState() {
|
|
nsGlobalWindowOuter* topWin = GetScriptableTopInternal();
|
|
|
|
if (!topWin) {
|
|
// IsInModalState() getting called w/o a reachable top window is a bit
|
|
// iffy, but valid enough not to make noise about it. See bug 404828
|
|
return false;
|
|
}
|
|
|
|
return topWin->mModalStateDepth != 0;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::NotifyWindowIDDestroyed(const char* aTopic) {
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
new WindowDestroyedEvent(this, mWindowID, aTopic);
|
|
Dispatch(TaskCategory::Other, runnable.forget());
|
|
}
|
|
|
|
Element* nsGlobalWindowOuter::GetFrameElementOuter(
|
|
nsIPrincipal& aSubjectPrincipal) {
|
|
if (!mDocShell || mDocShell->GetIsMozBrowser()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Per HTML5, the frameElement getter returns null in cross-origin situations.
|
|
Element* element = GetRealFrameElementOuter();
|
|
if (!element) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!aSubjectPrincipal.SubsumesConsideringDomain(element->NodePrincipal())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
Element* nsGlobalWindowOuter::GetRealFrameElementOuter() {
|
|
if (!mDocShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> parent;
|
|
mDocShell->GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent));
|
|
|
|
if (!parent || parent == mDocShell) {
|
|
// We're at a chrome boundary, don't expose the chrome iframe
|
|
// element to content code.
|
|
return nullptr;
|
|
}
|
|
|
|
return mFrameElement;
|
|
}
|
|
|
|
/**
|
|
* nsIGlobalWindow::GetFrameElement (when called from C++) is just a wrapper
|
|
* around GetRealFrameElement.
|
|
*/
|
|
Element* nsGlobalWindowOuter::GetFrameElement() {
|
|
FORWARD_TO_INNER(GetFrameElement, (), nullptr);
|
|
}
|
|
|
|
namespace {
|
|
class ChildCommandDispatcher : public Runnable {
|
|
public:
|
|
ChildCommandDispatcher(nsPIWindowRoot* aRoot, nsIBrowserChild* aBrowserChild,
|
|
const nsAString& aAction)
|
|
: mozilla::Runnable("ChildCommandDispatcher"),
|
|
mRoot(aRoot),
|
|
mBrowserChild(aBrowserChild),
|
|
mAction(aAction) {}
|
|
|
|
NS_IMETHOD Run() override {
|
|
nsTArray<nsCString> enabledCommands, disabledCommands;
|
|
mRoot->GetEnabledDisabledCommands(enabledCommands, disabledCommands);
|
|
if (enabledCommands.Length() || disabledCommands.Length()) {
|
|
mBrowserChild->EnableDisableCommands(mAction, enabledCommands,
|
|
disabledCommands);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsPIWindowRoot> mRoot;
|
|
nsCOMPtr<nsIBrowserChild> mBrowserChild;
|
|
nsString mAction;
|
|
};
|
|
|
|
class CommandDispatcher : public Runnable {
|
|
public:
|
|
CommandDispatcher(nsIDOMXULCommandDispatcher* aDispatcher,
|
|
const nsAString& aAction)
|
|
: mozilla::Runnable("CommandDispatcher"),
|
|
mDispatcher(aDispatcher),
|
|
mAction(aAction) {}
|
|
|
|
NS_IMETHOD Run() override { return mDispatcher->UpdateCommands(mAction); }
|
|
|
|
nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher;
|
|
nsString mAction;
|
|
};
|
|
} // anonymous namespace
|
|
|
|
void nsGlobalWindowOuter::UpdateCommands(const nsAString& anAction,
|
|
Selection* aSel, int16_t aReason) {
|
|
// If this is a child process, redirect to the parent process.
|
|
if (nsIDocShell* docShell = GetDocShell()) {
|
|
if (nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild()) {
|
|
nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
|
|
if (root) {
|
|
nsContentUtils::AddScriptRunner(
|
|
new ChildCommandDispatcher(root, child, anAction));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsPIDOMWindowOuter* rootWindow = GetPrivateRoot();
|
|
if (!rootWindow) {
|
|
return;
|
|
}
|
|
|
|
Document* doc = rootWindow->GetExtantDoc();
|
|
|
|
if (!doc) {
|
|
return;
|
|
}
|
|
// selectionchange action is only used for mozbrowser, not for XUL. So we
|
|
// bypass XUL command dispatch if anAction is "selectionchange".
|
|
if (!anAction.EqualsLiteral("selectionchange")) {
|
|
// Retrieve the command dispatcher and call updateCommands on it.
|
|
nsIDOMXULCommandDispatcher* xulCommandDispatcher =
|
|
doc->GetCommandDispatcher();
|
|
if (xulCommandDispatcher) {
|
|
nsContentUtils::AddScriptRunner(
|
|
new CommandDispatcher(xulCommandDispatcher, anAction));
|
|
}
|
|
}
|
|
}
|
|
|
|
Selection* nsGlobalWindowOuter::GetSelectionOuter() {
|
|
if (!mDocShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
PresShell* presShell = mDocShell->GetPresShell();
|
|
if (!presShell) {
|
|
return nullptr;
|
|
}
|
|
return presShell->GetCurrentSelection(SelectionType::eNormal);
|
|
}
|
|
|
|
already_AddRefed<Selection> nsGlobalWindowOuter::GetSelection() {
|
|
RefPtr<Selection> selection = GetSelectionOuter();
|
|
return selection.forget();
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::FindOuter(const nsAString& aString,
|
|
bool aCaseSensitive, bool aBackwards,
|
|
bool aWrapAround, bool aWholeWord,
|
|
bool aSearchInFrames, bool aShowDialog,
|
|
ErrorResult& aError) {
|
|
Unused << aShowDialog;
|
|
|
|
if (Preferences::GetBool("dom.disable_window_find", false)) {
|
|
aError.Throw(NS_ERROR_NOT_AVAILABLE);
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIWebBrowserFind> finder(do_GetInterface(mDocShell));
|
|
if (!finder) {
|
|
aError.Throw(NS_ERROR_NOT_AVAILABLE);
|
|
return false;
|
|
}
|
|
|
|
// Set the options of the search
|
|
aError = finder->SetSearchString(aString);
|
|
if (aError.Failed()) {
|
|
return false;
|
|
}
|
|
finder->SetMatchCase(aCaseSensitive);
|
|
finder->SetFindBackwards(aBackwards);
|
|
finder->SetWrapFind(aWrapAround);
|
|
finder->SetEntireWord(aWholeWord);
|
|
finder->SetSearchFrames(aSearchInFrames);
|
|
|
|
// the nsIWebBrowserFind is initialized to use this window
|
|
// as the search root, but uses focus to set the current search
|
|
// frame. If we're being called from JS (as here), this window
|
|
// should be the current search frame.
|
|
nsCOMPtr<nsIWebBrowserFindInFrames> framesFinder(do_QueryInterface(finder));
|
|
if (framesFinder) {
|
|
framesFinder->SetRootSearchFrame(this); // paranoia
|
|
framesFinder->SetCurrentSearchFrame(this);
|
|
}
|
|
|
|
if (aString.IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// Launch the search with the passed in search string
|
|
bool didFind = false;
|
|
aError = finder->FindNext(&didFind);
|
|
return didFind;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// EventTarget
|
|
//*****************************************************************************
|
|
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOwnerGlobalForBindingsInternal() {
|
|
return this;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::DispatchEvent(Event& aEvent, CallerType aCallerType,
|
|
ErrorResult& aRv) {
|
|
FORWARD_TO_INNER(DispatchEvent, (aEvent, aCallerType, aRv), false);
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
|
|
// It's OK that we just return false here on failure to create an
|
|
// inner. GetOrCreateListenerManager() will likewise fail, and then
|
|
// we won't be adding any listeners anyway.
|
|
FORWARD_TO_INNER_CREATE(ComputeDefaultWantsUntrusted, (aRv), false);
|
|
}
|
|
|
|
EventListenerManager* nsGlobalWindowOuter::GetOrCreateListenerManager() {
|
|
FORWARD_TO_INNER_CREATE(GetOrCreateListenerManager, (), nullptr);
|
|
}
|
|
|
|
EventListenerManager* nsGlobalWindowOuter::GetExistingListenerManager() const {
|
|
FORWARD_TO_INNER(GetExistingListenerManager, (), nullptr);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsPIDOMWindow
|
|
//*****************************************************************************
|
|
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetPrivateParent() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
|
|
|
|
if (this == parent) {
|
|
nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
|
|
if (!chromeElement)
|
|
return nullptr; // This is ok, just means a null parent.
|
|
|
|
Document* doc = chromeElement->GetComposedDoc();
|
|
if (!doc) return nullptr; // This is ok, just means a null parent.
|
|
|
|
return doc->GetWindow();
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetPrivateRoot() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> top = GetTop();
|
|
|
|
nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
|
|
if (chromeElement) {
|
|
Document* doc = chromeElement->GetComposedDoc();
|
|
if (doc) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent = doc->GetWindow();
|
|
if (parent) {
|
|
top = parent->GetTop();
|
|
}
|
|
}
|
|
}
|
|
|
|
return top;
|
|
}
|
|
|
|
// This has a caller in Windows-only code (nsNativeAppSupportWin).
|
|
Location* nsGlobalWindowOuter::GetLocation() {
|
|
// This method can be called on the outer window as well.
|
|
FORWARD_TO_INNER(Location, (), nullptr);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ActivateOrDeactivate(bool aActivate) {
|
|
if (!mDoc) {
|
|
return;
|
|
}
|
|
|
|
// Set / unset mIsActive on the top level window, which is used for the
|
|
// :-moz-window-inactive pseudoclass, and its sheet (if any).
|
|
nsCOMPtr<nsIWidget> mainWidget = GetMainWidget();
|
|
nsCOMPtr<nsIWidget> topLevelWidget;
|
|
if (mainWidget) {
|
|
// Get the top level widget (if the main widget is a sheet, this will
|
|
// be the sheet's top (non-sheet) parent).
|
|
topLevelWidget = mainWidget->GetSheetWindowParent();
|
|
if (!topLevelWidget) {
|
|
topLevelWidget = mainWidget;
|
|
}
|
|
}
|
|
|
|
SetActive(aActivate);
|
|
|
|
if (mainWidget != topLevelWidget) {
|
|
// This is a workaround for the following problem:
|
|
// When a window with an open sheet gains or loses focus, only the sheet
|
|
// window receives the NS_ACTIVATE/NS_DEACTIVATE event. However the
|
|
// styling of the containing top level window also needs to change. We
|
|
// get around this by calling nsPIDOMWindow::SetActive() on both windows.
|
|
|
|
// Get the top level widget's nsGlobalWindowOuter
|
|
nsCOMPtr<nsPIDOMWindowOuter> topLevelWindow;
|
|
|
|
// widgetListener should be a nsXULWindow
|
|
nsIWidgetListener* listener = topLevelWidget->GetWidgetListener();
|
|
if (listener) {
|
|
nsCOMPtr<nsIXULWindow> window = listener->GetXULWindow();
|
|
nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(window));
|
|
topLevelWindow = do_GetInterface(req);
|
|
}
|
|
|
|
if (topLevelWindow) {
|
|
topLevelWindow->SetActive(aActivate);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool NotifyDocumentTree(Document* aDocument, void* aData) {
|
|
aDocument->EnumerateSubDocuments(NotifyDocumentTree, nullptr);
|
|
aDocument->UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, true);
|
|
return true;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetActive(bool aActive) {
|
|
nsPIDOMWindowOuter::SetActive(aActive);
|
|
if (mDoc) {
|
|
NotifyDocumentTree(mDoc, nullptr);
|
|
}
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::IsTopLevelWindowActive() {
|
|
nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShell());
|
|
if (!treeItem) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> rootItem;
|
|
treeItem->GetRootTreeItem(getter_AddRefs(rootItem));
|
|
if (!rootItem) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> domWindow = rootItem->GetWindow();
|
|
return domWindow && domWindow->IsActive();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetIsBackground(bool aIsBackground) {
|
|
bool changed = aIsBackground != IsBackground();
|
|
SetIsBackgroundInternal(aIsBackground);
|
|
|
|
nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
|
|
|
|
if (inner && changed) {
|
|
inner->mTimeoutManager->UpdateBackgroundState();
|
|
}
|
|
|
|
if (aIsBackground) {
|
|
// Notify gamepadManager we are at the background window,
|
|
// we need to stop vibrate.
|
|
// Stop the vr telemery time spent when it switches to
|
|
// the background window.
|
|
if (inner && changed) {
|
|
inner->StopGamepadHaptics();
|
|
inner->StopVRActivity();
|
|
// true is for asking to set the delta time to
|
|
// the telemetry.
|
|
inner->ResetVRTelemetry(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (inner) {
|
|
// When switching to be as a top tab, restart the telemetry.
|
|
// false is for only resetting the timestamp.
|
|
inner->ResetVRTelemetry(false);
|
|
inner->SyncGamepadState();
|
|
inner->StartVRActivity();
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetIsBackgroundInternal(bool aIsBackground) {
|
|
if (mIsBackground != aIsBackground) {
|
|
TabGroup()->WindowChangedBackgroundStatus(aIsBackground);
|
|
}
|
|
mIsBackground = aIsBackground;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetChromeEventHandler(
|
|
EventTarget* aChromeEventHandler) {
|
|
SetChromeEventHandlerInternal(aChromeEventHandler);
|
|
// update the chrome event handler on all our inner windows
|
|
RefPtr<nsGlobalWindowInner> inner;
|
|
for (PRCList* node = PR_LIST_HEAD(this); node != this;
|
|
node = PR_NEXT_LINK(inner)) {
|
|
// This cast is only safe if `node != this`, as nsGlobalWindowOuter is also
|
|
// in the list.
|
|
inner = static_cast<nsGlobalWindowInner*>(node);
|
|
NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == this,
|
|
"bad outer window pointer");
|
|
inner->SetChromeEventHandlerInternal(aChromeEventHandler);
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetFocusedElement(Element* aElement,
|
|
uint32_t aFocusMethod,
|
|
bool aNeedsFocus) {
|
|
FORWARD_TO_INNER_VOID(SetFocusedElement,
|
|
(aElement, aFocusMethod, aNeedsFocus));
|
|
}
|
|
|
|
uint32_t nsGlobalWindowOuter::GetFocusMethod() {
|
|
FORWARD_TO_INNER(GetFocusMethod, (), 0);
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::ShouldShowFocusRing() {
|
|
FORWARD_TO_INNER(ShouldShowFocusRing, (), false);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetKeyboardIndicators(
|
|
UIStateChangeType aShowFocusRings) {
|
|
nsPIDOMWindowOuter* piWin = GetPrivateRoot();
|
|
if (!piWin) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(piWin == this);
|
|
|
|
bool oldShouldShowFocusRing = ShouldShowFocusRing();
|
|
|
|
// only change the flags that have been modified
|
|
nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler);
|
|
if (!windowRoot) {
|
|
return;
|
|
}
|
|
|
|
if (aShowFocusRings != UIStateChangeType_NoChange) {
|
|
windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set);
|
|
}
|
|
|
|
nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(this, aShowFocusRings);
|
|
|
|
bool newShouldShowFocusRing = ShouldShowFocusRing();
|
|
if (mInnerWindow && nsGlobalWindowInner::Cast(mInnerWindow)->mHasFocus &&
|
|
mInnerWindow->mFocusedElement &&
|
|
oldShouldShowFocusRing != newShouldShowFocusRing) {
|
|
// Update focusedNode's state.
|
|
if (newShouldShowFocusRing) {
|
|
mInnerWindow->mFocusedElement->AddStates(NS_EVENT_STATE_FOCUSRING);
|
|
} else {
|
|
mInnerWindow->mFocusedElement->RemoveStates(NS_EVENT_STATE_FOCUSRING);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::TakeFocus(bool aFocus, uint32_t aFocusMethod) {
|
|
FORWARD_TO_INNER(TakeFocus, (aFocus, aFocusMethod), false);
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetReadyForFocus() {
|
|
FORWARD_TO_INNER_VOID(SetReadyForFocus, ());
|
|
}
|
|
|
|
void nsGlobalWindowOuter::PageHidden() {
|
|
FORWARD_TO_INNER_VOID(PageHidden, ());
|
|
}
|
|
|
|
already_AddRefed<nsICSSDeclaration>
|
|
nsGlobalWindowOuter::GetComputedStyleHelperOuter(Element& aElt,
|
|
const nsAString& aPseudoElt,
|
|
bool aDefaultStylesOnly) {
|
|
if (!mDoc) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<nsICSSDeclaration> compStyle = NS_NewComputedDOMStyle(
|
|
&aElt, aPseudoElt, mDoc,
|
|
aDefaultStylesOnly ? nsComputedDOMStyle::eDefaultOnly
|
|
: nsComputedDOMStyle::eAll);
|
|
|
|
return compStyle.forget();
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsIInterfaceRequestor
|
|
//*****************************************************************************
|
|
|
|
nsresult nsGlobalWindowOuter::GetInterfaceInternal(const nsIID& aIID,
|
|
void** aSink) {
|
|
NS_ENSURE_ARG_POINTER(aSink);
|
|
*aSink = nullptr;
|
|
|
|
if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) {
|
|
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
|
|
webNav.forget(aSink);
|
|
} else if (aIID.Equals(NS_GET_IID(nsIDocShell))) {
|
|
nsCOMPtr<nsIDocShell> docShell = mDocShell;
|
|
docShell.forget(aSink);
|
|
}
|
|
#ifdef NS_PRINTING
|
|
else if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) {
|
|
if (mDocShell) {
|
|
nsCOMPtr<nsIContentViewer> viewer;
|
|
mDocShell->GetContentViewer(getter_AddRefs(viewer));
|
|
if (viewer) {
|
|
nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer));
|
|
webBrowserPrint.forget(aSink);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
else if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
|
|
nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(mDocShell));
|
|
loadContext.forget(aSink);
|
|
}
|
|
|
|
return *aSink ? NS_OK : NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsGlobalWindowOuter::GetInterface(const nsIID& aIID, void** aSink) {
|
|
nsresult rv = GetInterfaceInternal(aIID, aSink);
|
|
if (rv == NS_ERROR_NO_INTERFACE) {
|
|
return QueryInterface(aIID, aSink);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter::nsIObserver
|
|
//*****************************************************************************
|
|
|
|
NS_IMETHODIMP
|
|
nsGlobalWindowOuter::Observe(nsISupports* aSupports, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!nsCRT::strcmp(aTopic, PERM_CHANGE_NOTIFICATION)) {
|
|
nsCOMPtr<nsIPermission> permission = do_QueryInterface(aSupports);
|
|
if (!permission) {
|
|
return NS_OK;
|
|
}
|
|
nsIPrincipal* principal = GetPrincipal();
|
|
if (!principal) {
|
|
return NS_OK;
|
|
}
|
|
if (!AntiTrackingCommon::IsStorageAccessPermission(permission, principal)) {
|
|
return NS_OK;
|
|
}
|
|
if (!nsCRT::strcmp(aData, u"deleted")) {
|
|
// The storage access permission was deleted.
|
|
mHasStorageAccess = false;
|
|
return NS_OK;
|
|
}
|
|
if (!nsCRT::strcmp(aData, u"added") || !nsCRT::strcmp(aData, u"changed")) {
|
|
// The storage access permission was granted or modified.
|
|
uint32_t expireType = 0;
|
|
int64_t expireTime = 0;
|
|
MOZ_ALWAYS_SUCCEEDS(permission->GetExpireType(&expireType));
|
|
MOZ_ALWAYS_SUCCEEDS(permission->GetExpireTime(&expireTime));
|
|
if ((expireType == nsIPermissionManager::EXPIRE_TIME &&
|
|
expireTime >= PR_Now() / 1000) ||
|
|
(expireType == nsIPermissionManager::EXPIRE_SESSION &&
|
|
expireTime != 0)) {
|
|
// Permission hasn't expired yet.
|
|
mHasStorageAccess = true;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::IsSuspended() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
// No inner means we are effectively suspended
|
|
if (!mInnerWindow) {
|
|
return true;
|
|
}
|
|
return mInnerWindow->IsSuspended();
|
|
}
|
|
|
|
bool nsGlobalWindowOuter::IsFrozen() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
// No inner means we are effectively frozen
|
|
if (!mInnerWindow) {
|
|
return true;
|
|
}
|
|
return mInnerWindow->IsFrozen();
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::FireDelayedDOMEvents() {
|
|
FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter: Window Control Functions
|
|
//*****************************************************************************
|
|
|
|
nsPIDOMWindowOuter* nsGlobalWindowOuter::GetParentInternal() {
|
|
nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
|
|
|
|
if (parent && parent != this) {
|
|
return parent;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::UnblockScriptedClosing() {
|
|
mBlockScriptedClosingFlag = false;
|
|
}
|
|
|
|
class AutoUnblockScriptClosing {
|
|
private:
|
|
RefPtr<nsGlobalWindowOuter> mWin;
|
|
|
|
public:
|
|
explicit AutoUnblockScriptClosing(nsGlobalWindowOuter* aWin) : mWin(aWin) {
|
|
MOZ_ASSERT(mWin);
|
|
}
|
|
~AutoUnblockScriptClosing() {
|
|
void (nsGlobalWindowOuter::*run)() =
|
|
&nsGlobalWindowOuter::UnblockScriptedClosing;
|
|
nsCOMPtr<nsIRunnable> caller = NewRunnableMethod(
|
|
"AutoUnblockScriptClosing::~AutoUnblockScriptClosing", mWin, run);
|
|
mWin->Dispatch(TaskCategory::Other, caller.forget());
|
|
}
|
|
};
|
|
|
|
nsresult nsGlobalWindowOuter::OpenInternal(
|
|
const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
|
|
bool aDialog, bool aContentModal, bool aCalledNoScript, bool aDoJSFixups,
|
|
bool aNavigate, nsIArray* argv, nsISupports* aExtraArgument,
|
|
nsDocShellLoadState* aLoadState, bool aForceNoOpener,
|
|
nsPIDOMWindowOuter** aReturn) {
|
|
#ifdef DEBUG
|
|
uint32_t argc = 0;
|
|
if (argv) argv->GetLength(&argc);
|
|
#endif
|
|
|
|
MOZ_ASSERT(!aExtraArgument || (!argv && argc == 0),
|
|
"Can't pass in arguments both ways");
|
|
MOZ_ASSERT(!aCalledNoScript || (!argv && argc == 0),
|
|
"Can't pass JS args when called via the noscript methods");
|
|
|
|
mozilla::Maybe<AutoUnblockScriptClosing> closeUnblocker;
|
|
|
|
// Calls to window.open from script should navigate.
|
|
MOZ_ASSERT(aCalledNoScript || aNavigate);
|
|
|
|
*aReturn = nullptr;
|
|
|
|
nsCOMPtr<nsIWebBrowserChrome> chrome = GetWebBrowserChrome();
|
|
if (!chrome) {
|
|
// No chrome means we don't want to go through with this open call
|
|
// -- see nsIWindowWatcher.idl
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_ASSERTION(mDocShell, "Must have docshell here");
|
|
|
|
nsAutoCString options;
|
|
bool forceNoOpener = aForceNoOpener;
|
|
bool forceNoReferrer = false;
|
|
// Unlike other window flags, "noopener" comes from splitting on commas with
|
|
// HTML whitespace trimming...
|
|
nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
|
|
aOptions, ',');
|
|
while (tok.hasMoreTokens()) {
|
|
auto nextTok = tok.nextToken();
|
|
if (nextTok.EqualsLiteral("noopener")) {
|
|
forceNoOpener = true;
|
|
continue;
|
|
}
|
|
if (StaticPrefs::dom_window_open_noreferrer_enabled() &&
|
|
nextTok.LowerCaseEqualsLiteral("noreferrer")) {
|
|
forceNoReferrer = true;
|
|
// noreferrer implies noopener
|
|
forceNoOpener = true;
|
|
continue;
|
|
}
|
|
// Want to create a copy of the options without 'noopener' because having
|
|
// 'noopener' in the options affects other window features.
|
|
if (!options.IsEmpty()) {
|
|
options.Append(',');
|
|
}
|
|
AppendUTF16toUTF8(nextTok, options);
|
|
}
|
|
|
|
bool windowExists = WindowExists(aName, forceNoOpener, !aCalledNoScript);
|
|
|
|
// XXXbz When this gets fixed to not use LegacyIsCallerNativeCode()
|
|
// (indirectly) maybe we can nix the AutoJSAPI usage OnLinkClickEvent::Run.
|
|
// But note that if you change this to GetEntryGlobal(), say, then
|
|
// OnLinkClickEvent::Run will need a full-blown AutoEntryScript.
|
|
const bool checkForPopup =
|
|
!nsContentUtils::LegacyIsCallerChromeOrNativeCode() && !aDialog &&
|
|
!windowExists;
|
|
|
|
// Note: the Void handling here is very important, because the window watcher
|
|
// expects a null URL string (not an empty string) if there is no URL to load.
|
|
nsCString url;
|
|
url.SetIsVoid(true);
|
|
nsresult rv = NS_OK;
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
|
|
// It's important to do this security check before determining whether this
|
|
// window opening should be blocked, to ensure that we don't FireAbuseEvents
|
|
// for a window opening that wouldn't have succeeded in the first place.
|
|
if (!aUrl.IsEmpty()) {
|
|
AppendUTF16toUTF8(aUrl, url);
|
|
|
|
// It's safe to skip the security check below if we're not a dialog
|
|
// because window.openDialog is not callable from content script. See bug
|
|
// 56851.
|
|
//
|
|
// If we're not navigating, we assume that whoever *does* navigate the
|
|
// window will do a security check of their own.
|
|
if (!url.IsVoid() && !aDialog && aNavigate)
|
|
rv = SecurityCheckURL(url.get(), getter_AddRefs(uri));
|
|
}
|
|
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PopupBlocker::PopupControlState abuseLevel =
|
|
PopupBlocker::GetPopupControlState();
|
|
if (checkForPopup) {
|
|
abuseLevel = RevisePopupAbuseLevel(abuseLevel);
|
|
if (abuseLevel >= PopupBlocker::openBlocked) {
|
|
if (!aCalledNoScript) {
|
|
// If script in some other window is doing a window.open on us and
|
|
// it's being blocked, then it's OK to close us afterwards, probably.
|
|
// But if we're doing a window.open on ourselves and block the popup,
|
|
// prevent this window from closing until after this script terminates
|
|
// so that whatever popup blocker UI the app has will be visible.
|
|
nsCOMPtr<nsPIDOMWindowInner> entryWindow =
|
|
do_QueryInterface(GetEntryGlobal());
|
|
// Note that entryWindow can be null here if some JS component was the
|
|
// place where script was entered for this JS execution.
|
|
if (entryWindow && entryWindow->GetOuterWindow() == this) {
|
|
mBlockScriptedClosingFlag = true;
|
|
closeUnblocker.emplace(this);
|
|
}
|
|
}
|
|
|
|
FireAbuseEvents(aUrl, aName, aOptions);
|
|
return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> domReturn;
|
|
|
|
nsCOMPtr<nsIWindowWatcher> wwatch =
|
|
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
|
|
NS_ENSURE_TRUE(wwatch, rv);
|
|
|
|
NS_ConvertUTF16toUTF8 name(aName);
|
|
|
|
const char* options_ptr = options.IsEmpty() ? nullptr : options.get();
|
|
const char* name_ptr = aName.IsEmpty() ? nullptr : name.get();
|
|
|
|
nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
|
|
NS_ENSURE_STATE(pwwatch);
|
|
|
|
MOZ_ASSERT_IF(checkForPopup, abuseLevel < PopupBlocker::openBlocked);
|
|
// At this point we should know for a fact that if checkForPopup then
|
|
// abuseLevel < PopupBlocker::openBlocked, so we could just check for
|
|
// abuseLevel == PopupBlocker::openControlled. But let's be defensive just in
|
|
// case and treat anything that fails the above assert as a spam popup too, if
|
|
// it ever happens.
|
|
bool isPopupSpamWindow =
|
|
checkForPopup && (abuseLevel >= PopupBlocker::openControlled);
|
|
|
|
{
|
|
// Reset popup state while opening a window to prevent the
|
|
// current state from being active the whole time a modal
|
|
// dialog is open.
|
|
AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
|
|
|
|
if (!aCalledNoScript) {
|
|
// We asserted at the top of this function that aNavigate is true for
|
|
// !aCalledNoScript.
|
|
rv = pwwatch->OpenWindow2(
|
|
this, url.IsVoid() ? nullptr : url.get(), name_ptr, options_ptr,
|
|
/* aCalledFromScript = */ true, aDialog, aNavigate, argv,
|
|
isPopupSpamWindow, forceNoOpener, forceNoReferrer, aLoadState,
|
|
getter_AddRefs(domReturn));
|
|
} else {
|
|
// Force a system caller here so that the window watcher won't screw us
|
|
// up. We do NOT want this case looking at the JS context on the stack
|
|
// when searching. Compare comments on
|
|
// nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow.
|
|
|
|
// Note: Because nsWindowWatcher is so broken, it's actually important
|
|
// that we don't force a system caller here, because that screws it up
|
|
// when it tries to compute the caller principal to associate with dialog
|
|
// arguments. That whole setup just really needs to be rewritten. :-(
|
|
Maybe<AutoNoJSAPI> nojsapi;
|
|
if (!aContentModal) {
|
|
nojsapi.emplace();
|
|
}
|
|
|
|
rv = pwwatch->OpenWindow2(
|
|
this, url.IsVoid() ? nullptr : url.get(), name_ptr, options_ptr,
|
|
/* aCalledFromScript = */ false, aDialog, aNavigate, aExtraArgument,
|
|
isPopupSpamWindow, forceNoOpener, forceNoReferrer, aLoadState,
|
|
getter_AddRefs(domReturn));
|
|
}
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// success!
|
|
|
|
if (!aCalledNoScript && !windowExists && uri && !forceNoOpener) {
|
|
MaybeAllowStorageForOpenedWindow(uri);
|
|
}
|
|
|
|
if (domReturn && aDoJSFixups) {
|
|
nsCOMPtr<nsIDOMChromeWindow> chrome_win(do_QueryInterface(domReturn));
|
|
if (!chrome_win) {
|
|
// A new non-chrome window was created from a call to
|
|
// window.open() from JavaScript, make sure there's a document in
|
|
// the new window. We do this by simply asking the new window for
|
|
// its document, this will synchronously create an empty document
|
|
// if there is no document in the window.
|
|
// XXXbz should this just use EnsureInnerWindow()?
|
|
|
|
// Force document creation.
|
|
nsCOMPtr<Document> doc = nsPIDOMWindowOuter::From(domReturn)->GetDoc();
|
|
Unused << doc;
|
|
}
|
|
}
|
|
|
|
*aReturn = do_AddRef(nsPIDOMWindowOuter::From(domReturn)).take();
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::MaybeAllowStorageForOpenedWindow(nsIURI* aURI) {
|
|
nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
|
|
if (NS_WARN_IF(!inner)) {
|
|
return;
|
|
}
|
|
|
|
// No 3rd party URL/window.
|
|
if (!nsContentUtils::IsThirdPartyWindowOrChannel(inner, nullptr, aURI)) {
|
|
return;
|
|
}
|
|
|
|
Document* doc = inner->GetDoc();
|
|
if (!doc) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
|
|
aURI, doc->NodePrincipal()->OriginAttributesRef());
|
|
|
|
// We don't care when the asynchronous work finishes here.
|
|
Unused << AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
|
|
principal, inner, AntiTrackingCommon::eOpener);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsGlobalWindowOuter: Helper Functions
|
|
//*****************************************************************************
|
|
|
|
already_AddRefed<nsIDocShellTreeOwner> nsGlobalWindowOuter::GetTreeOwner() {
|
|
// If there's no docShellAsItem, this window must have been closed,
|
|
// in that case there is no tree owner.
|
|
|
|
if (!mDocShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
|
mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
|
|
return treeOwner.forget();
|
|
}
|
|
|
|
already_AddRefed<nsIBaseWindow> nsGlobalWindowOuter::GetTreeOwnerWindow() {
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
|
|
|
// If there's no mDocShell, this window must have been closed,
|
|
// in that case there is no tree owner.
|
|
|
|
if (mDocShell) {
|
|
mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
|
|
}
|
|
|
|
nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
|
|
return baseWindow.forget();
|
|
}
|
|
|
|
already_AddRefed<nsIWebBrowserChrome>
|
|
nsGlobalWindowOuter::GetWebBrowserChrome() {
|
|
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
|
|
|
|
nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
|
|
return browserChrome.forget();
|
|
}
|
|
|
|
nsIScrollableFrame* nsGlobalWindowOuter::GetScrollFrame() {
|
|
if (!mDocShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
PresShell* presShell = mDocShell->GetPresShell();
|
|
if (presShell) {
|
|
return presShell->GetRootScrollFrameAsScrollable();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::SecurityCheckURL(const char* aURL,
|
|
nsIURI** aURI) {
|
|
nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
|
|
do_QueryInterface(GetEntryGlobal());
|
|
if (!sourceWindow) {
|
|
sourceWindow = GetCurrentInnerWindow();
|
|
}
|
|
AutoJSContext cx;
|
|
nsGlobalWindowInner* sourceWin = nsGlobalWindowInner::Cast(sourceWindow);
|
|
JSAutoRealm ar(cx, sourceWin->GetGlobalJSObject());
|
|
|
|
// Resolve the baseURI, which could be relative to the calling window.
|
|
//
|
|
// Note the algorithm to get the base URI should match the one
|
|
// used to actually kick off the load in nsWindowWatcher.cpp.
|
|
nsCOMPtr<Document> doc = sourceWindow->GetDoc();
|
|
nsIURI* baseURI = nullptr;
|
|
auto encoding = UTF_8_ENCODING; // default to utf-8
|
|
if (doc) {
|
|
baseURI = doc->GetDocBaseURI();
|
|
encoding = doc->GetDocumentCharacterSet();
|
|
}
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL),
|
|
encoding, baseURI);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckLoadURIFromScript(
|
|
cx, uri))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
uri.forget(aURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::FlushPendingNotifications(FlushType aType) {
|
|
if (mDoc) {
|
|
mDoc->FlushPendingNotifications(aType);
|
|
}
|
|
}
|
|
|
|
void nsGlobalWindowOuter::EnsureSizeAndPositionUpToDate() {
|
|
// If we're a subframe, make sure our size is up to date. Make sure to go
|
|
// through the document chain rather than the window chain to not flush on
|
|
// detached iframes, see bug 1545516.
|
|
if (mDoc && mDoc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
|
|
RefPtr<Document> parent = mDoc->GetParentDocument();
|
|
parent->FlushPendingNotifications(FlushType::Layout);
|
|
}
|
|
}
|
|
|
|
already_AddRefed<nsISupports> nsGlobalWindowOuter::SaveWindowState() {
|
|
if (!mContext || !GetWrapperPreserveColor()) {
|
|
// The window may be getting torn down; don't bother saving state.
|
|
return nullptr;
|
|
}
|
|
|
|
nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
|
|
NS_ASSERTION(inner, "No inner window to save");
|
|
|
|
// Don't do anything else to this inner window! After this point, all
|
|
// calls to SetTimeoutOrInterval will create entries in the timeout
|
|
// list that will only run after this window has come out of the bfcache.
|
|
// Also, while we're frozen, we won't dispatch online/offline events
|
|
// to the page.
|
|
inner->Freeze();
|
|
|
|
nsCOMPtr<nsISupports> state = new WindowStateHolder(inner);
|
|
|
|
MOZ_LOG(gPageCacheLog, LogLevel::Debug,
|
|
("saving window state, state = %p", (void*)state));
|
|
|
|
return state.forget();
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::RestoreWindowState(nsISupports* aState) {
|
|
if (!mContext || !GetWrapperPreserveColor()) {
|
|
// The window may be getting torn down; don't bother restoring state.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<WindowStateHolder> holder = do_QueryInterface(aState);
|
|
NS_ENSURE_TRUE(holder, NS_ERROR_FAILURE);
|
|
|
|
MOZ_LOG(gPageCacheLog, LogLevel::Debug,
|
|
("restoring window state, state = %p", (void*)holder));
|
|
|
|
// And we're ready to go!
|
|
nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
|
|
|
|
// if a link is focused, refocus with the FLAG_SHOWRING flag set. This makes
|
|
// it easy to tell which link was last clicked when going back a page.
|
|
Element* focusedElement = inner->GetFocusedElement();
|
|
if (nsContentUtils::ContentIsLink(focusedElement)) {
|
|
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (fm) {
|
|
// XXXbz Do we need the stack strong ref here?
|
|
RefPtr<Element> kungFuDeathGrip(focusedElement);
|
|
fm->SetFocus(kungFuDeathGrip, nsIFocusManager::FLAG_NOSCROLL |
|
|
nsIFocusManager::FLAG_SHOWRING);
|
|
}
|
|
}
|
|
|
|
inner->Thaw();
|
|
|
|
holder->DidRestoreWindow();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::AddSizeOfIncludingThis(
|
|
nsWindowSizes& aWindowSizes) const {
|
|
aWindowSizes.mDOMOtherSize += aWindowSizes.mState.mMallocSizeOf(this);
|
|
}
|
|
|
|
uint32_t nsGlobalWindowOuter::GetAutoActivateVRDisplayID() {
|
|
uint32_t retVal = mAutoActivateVRDisplayID;
|
|
mAutoActivateVRDisplayID = 0;
|
|
return retVal;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetAutoActivateVRDisplayID(
|
|
uint32_t aAutoActivateVRDisplayID) {
|
|
mAutoActivateVRDisplayID = aAutoActivateVRDisplayID;
|
|
}
|
|
|
|
already_AddRefed<nsWindowRoot> nsGlobalWindowOuter::GetWindowRootOuter() {
|
|
nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
|
|
return root.forget().downcast<nsWindowRoot>();
|
|
}
|
|
|
|
nsIDOMWindowUtils* nsGlobalWindowOuter::WindowUtils() {
|
|
if (!mWindowUtils) {
|
|
mWindowUtils = new nsDOMWindowUtils(this);
|
|
}
|
|
return mWindowUtils;
|
|
}
|
|
|
|
// Note: This call will lock the cursor, it will not change as it moves.
|
|
// To unlock, the cursor must be set back to Auto.
|
|
void nsGlobalWindowOuter::SetCursorOuter(const nsAString& aCursor,
|
|
ErrorResult& aError) {
|
|
StyleCursorKind cursor;
|
|
|
|
if (aCursor.EqualsLiteral("auto")) {
|
|
cursor = StyleCursorKind::Auto;
|
|
} else {
|
|
// TODO(emilio): Use Servo for this instead.
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aCursor);
|
|
int32_t c;
|
|
if (!nsCSSProps::FindKeyword(keyword, nsCSSProps::kCursorKTable, c)) {
|
|
return;
|
|
}
|
|
cursor = static_cast<StyleCursorKind>(c);
|
|
}
|
|
|
|
RefPtr<nsPresContext> presContext;
|
|
if (mDocShell) {
|
|
presContext = mDocShell->GetPresContext();
|
|
}
|
|
|
|
if (presContext) {
|
|
// Need root widget.
|
|
PresShell* presShell = mDocShell->GetPresShell();
|
|
if (!presShell) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
nsViewManager* vm = presShell->GetViewManager();
|
|
if (!vm) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
nsView* rootView = vm->GetRootView();
|
|
if (!rootView) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
nsIWidget* widget = rootView->GetNearestWidget(nullptr);
|
|
if (!widget) {
|
|
aError.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// Call esm and set cursor.
|
|
aError = presContext->EventStateManager()->SetCursor(
|
|
cursor, nullptr, Nothing(), widget, true);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsGlobalWindowOuter::GetBrowserDOMWindow(nsIBrowserDOMWindow** aBrowserWindow) {
|
|
MOZ_RELEASE_ASSERT(IsChromeWindow());
|
|
FORWARD_TO_INNER(GetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
nsIBrowserDOMWindow* nsGlobalWindowOuter::GetBrowserDOMWindowOuter() {
|
|
MOZ_ASSERT(IsChromeWindow());
|
|
return mChromeFields.mBrowserDOMWindow;
|
|
}
|
|
|
|
void nsGlobalWindowOuter::SetBrowserDOMWindowOuter(
|
|
nsIBrowserDOMWindow* aBrowserWindow) {
|
|
MOZ_ASSERT(IsChromeWindow());
|
|
mChromeFields.mBrowserDOMWindow = aBrowserWindow;
|
|
}
|
|
|
|
ChromeMessageBroadcaster* nsGlobalWindowOuter::GetMessageManager() {
|
|
if (!mInnerWindow) {
|
|
NS_WARNING("No inner window available!");
|
|
return nullptr;
|
|
}
|
|
return GetCurrentInnerWindowInternal()->MessageManager();
|
|
}
|
|
|
|
ChromeMessageBroadcaster* nsGlobalWindowOuter::GetGroupMessageManager(
|
|
const nsAString& aGroup) {
|
|
if (!mInnerWindow) {
|
|
NS_WARNING("No inner window available!");
|
|
return nullptr;
|
|
}
|
|
return GetCurrentInnerWindowInternal()->GetGroupMessageManager(aGroup);
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::SetOpenerForInitialContentBrowser(
|
|
BrowsingContext* aOpenerWindow) {
|
|
MOZ_ASSERT(!mOpenerForInitialContentBrowser,
|
|
"Don't set OpenerForInitialContentBrowser twice!");
|
|
mOpenerForInitialContentBrowser = aOpenerWindow;
|
|
}
|
|
|
|
already_AddRefed<BrowsingContext>
|
|
nsPIDOMWindowOuter::TakeOpenerForInitialContentBrowser() {
|
|
// Intentionally forget our own member
|
|
return mOpenerForInitialContentBrowser.forget();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::InitWasOffline() { mWasOffline = NS_IsOffline(); }
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
int16_t nsGlobalWindowOuter::Orientation(CallerType aCallerType) const {
|
|
return nsContentUtils::ResistFingerprinting(aCallerType)
|
|
? 0
|
|
: WindowOrientationObserver::OrientationAngle();
|
|
}
|
|
#endif
|
|
|
|
void nsPIDOMWindowOuter::SetLargeAllocStatus(LargeAllocStatus aStatus) {
|
|
MOZ_ASSERT(mLargeAllocStatus == LargeAllocStatus::NONE);
|
|
mLargeAllocStatus = aStatus;
|
|
}
|
|
|
|
bool nsPIDOMWindowOuter::IsTopLevelWindow() {
|
|
return nsGlobalWindowOuter::Cast(this)->IsTopLevelWindow();
|
|
}
|
|
|
|
bool nsPIDOMWindowOuter::HadOriginalOpener() const {
|
|
return nsGlobalWindowOuter::Cast(this)->HadOriginalOpener();
|
|
}
|
|
|
|
void nsGlobalWindowOuter::ReportLargeAllocStatus() {
|
|
uint32_t errorFlags = nsIScriptError::warningFlag;
|
|
const char* message = nullptr;
|
|
|
|
switch (mLargeAllocStatus) {
|
|
case LargeAllocStatus::SUCCESS:
|
|
// Override the error flags such that the success message isn't reported
|
|
// as a warning.
|
|
errorFlags = nsIScriptError::infoFlag;
|
|
message = "LargeAllocationSuccess";
|
|
break;
|
|
case LargeAllocStatus::NON_WIN32:
|
|
errorFlags = nsIScriptError::infoFlag;
|
|
message = "LargeAllocationNonWin32";
|
|
break;
|
|
case LargeAllocStatus::NON_GET:
|
|
message = "LargeAllocationNonGetRequest";
|
|
break;
|
|
case LargeAllocStatus::NON_E10S:
|
|
message = "LargeAllocationNonE10S";
|
|
break;
|
|
case LargeAllocStatus::NOT_ONLY_TOPLEVEL_IN_TABGROUP:
|
|
message = "LargeAllocationNotOnlyToplevelInTabGroup";
|
|
break;
|
|
default: // LargeAllocStatus::NONE
|
|
return; // Don't report a message to the console
|
|
}
|
|
|
|
nsContentUtils::ReportToConsole(errorFlags, NS_LITERAL_CSTRING("DOM"), mDoc,
|
|
nsContentUtils::eDOM_PROPERTIES, message);
|
|
}
|
|
|
|
#if defined(_WINDOWS_) && !defined(MOZ_WRAPPED_WINDOWS_H)
|
|
# pragma message( \
|
|
"wrapper failure reason: " MOZ_WINDOWS_WRAPPER_DISABLED_REASON)
|
|
# error "Never include unwrapped windows.h in this file!"
|
|
#endif
|
|
|
|
// Helper called by methods that move/resize the window,
|
|
// to ensure the presContext (if any) is aware of resolution
|
|
// change that may happen in multi-monitor configuration.
|
|
void nsGlobalWindowOuter::CheckForDPIChange() {
|
|
if (mDocShell) {
|
|
RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
|
|
if (presContext) {
|
|
if (presContext->DeviceContext()->CheckDPIChange()) {
|
|
presContext->UIResolutionChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mozilla::dom::TabGroup* nsGlobalWindowOuter::TabGroupOuter() {
|
|
// Outer windows lazily join TabGroups when requested. This is usually done
|
|
// because a document is getting its NodePrincipal, and asking for the
|
|
// TabGroup to determine its DocGroup.
|
|
if (!mTabGroup) {
|
|
// Get mOpener ourselves, instead of relying on GetOpenerWindowOuter,
|
|
// because that way we dodge the LegacyIsCallerChromeOrNativeCode() call
|
|
// which we want to return false.
|
|
nsCOMPtr<nsPIDOMWindowOuter> piOpener = do_QueryReferent(mOpener);
|
|
nsPIDOMWindowOuter* opener = GetSanitizedOpener(piOpener);
|
|
nsPIDOMWindowOuter* parent = GetScriptableParentOrNull();
|
|
MOZ_ASSERT(!parent || !opener,
|
|
"Only one of parent and opener may be provided");
|
|
|
|
mozilla::dom::TabGroup* toJoin = nullptr;
|
|
if (GetDocShell()->ItemType() == nsIDocShellTreeItem::typeChrome) {
|
|
toJoin = TabGroup::GetChromeTabGroup();
|
|
} else if (opener) {
|
|
toJoin = opener->TabGroup();
|
|
} else if (parent) {
|
|
toJoin = parent->TabGroup();
|
|
} else {
|
|
toJoin = TabGroup::GetFromWindow(this);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// Make sure that, if we have a tab group from the actor, it matches the one
|
|
// we're planning to join.
|
|
mozilla::dom::TabGroup* testGroup = TabGroup::GetFromWindow(this);
|
|
MOZ_ASSERT_IF(testGroup, testGroup == toJoin);
|
|
#endif
|
|
|
|
mTabGroup = mozilla::dom::TabGroup::Join(this, toJoin);
|
|
}
|
|
MOZ_ASSERT(mTabGroup);
|
|
|
|
#ifdef DEBUG
|
|
// Ensure that we don't recurse forever
|
|
if (!mIsValidatingTabGroup) {
|
|
mIsValidatingTabGroup = true;
|
|
// We only need to do this check if we aren't in the chrome tab group
|
|
if (mIsChrome) {
|
|
MOZ_ASSERT(mTabGroup == TabGroup::GetChromeTabGroup());
|
|
} else {
|
|
// Sanity check that our tabgroup matches our opener or parent.
|
|
RefPtr<nsPIDOMWindowOuter> parent = GetScriptableParentOrNull();
|
|
MOZ_ASSERT_IF(parent, parent->TabGroup() == mTabGroup);
|
|
nsCOMPtr<nsPIDOMWindowOuter> piOpener = do_QueryReferent(mOpener);
|
|
nsPIDOMWindowOuter* opener = GetSanitizedOpener(piOpener);
|
|
MOZ_ASSERT_IF(opener && nsGlobalWindowOuter::Cast(opener) != this,
|
|
opener->TabGroup() == mTabGroup);
|
|
}
|
|
mIsValidatingTabGroup = false;
|
|
}
|
|
#endif
|
|
|
|
return mTabGroup;
|
|
}
|
|
|
|
nsresult nsGlobalWindowOuter::Dispatch(
|
|
TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
if (GetDocGroup()) {
|
|
return GetDocGroup()->Dispatch(aCategory, std::move(aRunnable));
|
|
}
|
|
return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
|
|
}
|
|
|
|
nsISerialEventTarget* nsGlobalWindowOuter::EventTargetFor(
|
|
TaskCategory aCategory) const {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
if (GetDocGroup()) {
|
|
return GetDocGroup()->EventTargetFor(aCategory);
|
|
}
|
|
return DispatcherTrait::EventTargetFor(aCategory);
|
|
}
|
|
|
|
AbstractThread* nsGlobalWindowOuter::AbstractMainThreadFor(
|
|
TaskCategory aCategory) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
if (GetDocGroup()) {
|
|
return GetDocGroup()->AbstractMainThreadFor(aCategory);
|
|
}
|
|
return DispatcherTrait::AbstractMainThreadFor(aCategory);
|
|
}
|
|
|
|
nsGlobalWindowOuter::TemporarilyDisableDialogs::TemporarilyDisableDialogs(
|
|
nsGlobalWindowOuter* aWindow MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
|
|
: mSavedDialogsEnabled(false) {
|
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
|
|
|
MOZ_ASSERT(aWindow);
|
|
nsGlobalWindowOuter* topWindowOuter = aWindow->GetScriptableTopInternal();
|
|
if (!topWindowOuter) {
|
|
NS_ERROR(
|
|
"nsGlobalWindowOuter::TemporarilyDisableDialogs used without a top "
|
|
"window?");
|
|
return;
|
|
}
|
|
|
|
// TODO: Warn if no top window?
|
|
nsGlobalWindowInner* topWindow =
|
|
topWindowOuter->GetCurrentInnerWindowInternal();
|
|
if (topWindow) {
|
|
mTopWindow = topWindow;
|
|
mSavedDialogsEnabled = mTopWindow->mAreDialogsEnabled;
|
|
mTopWindow->mAreDialogsEnabled = false;
|
|
}
|
|
}
|
|
|
|
nsGlobalWindowOuter::TemporarilyDisableDialogs::~TemporarilyDisableDialogs() {
|
|
if (mTopWindow) {
|
|
mTopWindow->mAreDialogsEnabled = mSavedDialogsEnabled;
|
|
}
|
|
}
|
|
|
|
mozilla::dom::TabGroup* nsPIDOMWindowOuter::TabGroup() {
|
|
return nsGlobalWindowOuter::Cast(this)->TabGroupOuter();
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<nsGlobalWindowOuter> nsGlobalWindowOuter::Create(
|
|
nsDocShell* aDocShell, bool aIsChrome) {
|
|
uint64_t outerWindowID = aDocShell->GetOuterWindowID();
|
|
RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter(outerWindowID);
|
|
if (aIsChrome) {
|
|
window->mIsChrome = true;
|
|
}
|
|
window->SetDocShell(aDocShell);
|
|
|
|
window->InitWasOffline();
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
// Delay calling AddObserver until we hit the event loop, in case we may be
|
|
// in the middle of modifying the observer list somehow.
|
|
NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction("PermChangeDelayRunnable", [obs, window] {
|
|
obs->AddObserver(window, PERM_CHANGE_NOTIFICATION, true);
|
|
}));
|
|
}
|
|
return window.forget();
|
|
}
|
|
|
|
nsIURI* nsPIDOMWindowOuter::GetDocumentURI() const {
|
|
return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::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> document = docShell->GetDocument();
|
|
Unused << document;
|
|
}
|
|
}
|
|
|
|
void nsPIDOMWindowOuter::SetChromeEventHandlerInternal(
|
|
EventTarget* aChromeEventHandler) {
|
|
// Out-of-line so we don't need to include ContentFrameMessageManager.h in
|
|
// nsPIDOMWindow.h.
|
|
mChromeEventHandler = aChromeEventHandler;
|
|
|
|
// mParentTarget and mMessageManager will be set when the next event is
|
|
// dispatched or someone asks for our message manager.
|
|
mParentTarget = nullptr;
|
|
mMessageManager = nullptr;
|
|
}
|
|
|
|
mozilla::dom::DocGroup* nsPIDOMWindowOuter::GetDocGroup() const {
|
|
Document* doc = GetExtantDoc();
|
|
if (doc) {
|
|
return doc->GetDocGroup();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
nsPIDOMWindowOuter::nsPIDOMWindowOuter(uint64_t aWindowID)
|
|
: mFrameElement(nullptr),
|
|
mModalStateDepth(0),
|
|
mIsActive(false),
|
|
mIsBackground(false),
|
|
mMediaSuspend(
|
|
Preferences::GetBool("media.block-autoplay-until-in-foreground", true)
|
|
? nsISuspendedTypes::SUSPENDED_BLOCK
|
|
: nsISuspendedTypes::NONE_SUSPENDED),
|
|
mAudioMuted(false),
|
|
mAudioVolume(1.0),
|
|
mDesktopModeViewport(false),
|
|
mIsRootOuterWindow(false),
|
|
mInnerWindow(nullptr),
|
|
mWindowID(aWindowID),
|
|
mMarkedCCGeneration(0),
|
|
mServiceWorkersTestingEnabled(false),
|
|
mLargeAllocStatus(LargeAllocStatus::NONE) {}
|
|
|
|
nsPIDOMWindowOuter::~nsPIDOMWindowOuter() {}
|