mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-12-27 20:30:39 +00:00
#522: basic window.open=noopener, refactor popup blocker M1267338 M1267339
This commit is contained in:
parent
3660eb7e23
commit
46bf256a1f
@ -51,6 +51,7 @@
|
||||
#include "nsJSUtils.h"
|
||||
#include "jsapi.h" // for JSAutoRequest
|
||||
#include "jswrapper.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsDOMClassInfo.h"
|
||||
#include "nsJSEnvironment.h"
|
||||
@ -1483,9 +1484,20 @@ void
|
||||
nsGlobalWindow::MaybeForgiveSpamCount()
|
||||
{
|
||||
if (IsOuterWindow() &&
|
||||
IsPopupSpamWindow())
|
||||
{
|
||||
SetPopupSpamWindow(false);
|
||||
IsPopupSpamWindow()) {
|
||||
SetIsPopupSpamWindow(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsGlobalWindow::SetIsPopupSpamWindow(bool aIsPopupSpam)
|
||||
{
|
||||
MOZ_ASSERT(IsOuterWindow());
|
||||
|
||||
mIsPopupSpam = aIsPopupSpam;
|
||||
if (aIsPopupSpam) {
|
||||
++gOpenPopupSpamCount;
|
||||
} else {
|
||||
--gOpenPopupSpamCount;
|
||||
NS_ASSERTION(gOpenPopupSpamCount >= 0,
|
||||
"Unbalanced decrement of gOpenPopupSpamCount");
|
||||
@ -5718,11 +5730,18 @@ GetCallerDocShellTreeItem()
|
||||
|
||||
bool
|
||||
nsGlobalWindow::WindowExists(const nsAString& aName,
|
||||
bool aForceNoOpener,
|
||||
bool aLookForCallerOnJSStack)
|
||||
{
|
||||
NS_PRECONDITION(IsOuterWindow(), "Must be outer window");
|
||||
NS_PRECONDITION(mDocShell, "Must have docshell");
|
||||
|
||||
if (aForceNoOpener) {
|
||||
return aName.LowerCaseEqualsLiteral("_self") ||
|
||||
aName.LowerCaseEqualsLiteral("_top") ||
|
||||
aName.LowerCaseEqualsLiteral("_parent");
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocShellTreeItem> caller;
|
||||
if (aLookForCallerOnJSStack) {
|
||||
caller = GetCallerDocShellTreeItem();
|
||||
@ -7532,14 +7551,6 @@ nsGlobalWindow::FirePopupBlockedEvent(nsIDocument* aDoc,
|
||||
aDoc->DispatchEvent(event, &defaultActionEnabled);
|
||||
}
|
||||
|
||||
static void FirePopupWindowEvent(nsIDocument* aDoc)
|
||||
{
|
||||
// Fire a "PopupWindow" event
|
||||
nsContentUtils::DispatchTrustedEvent(aDoc, aDoc,
|
||||
NS_LITERAL_STRING("PopupWindow"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
// static
|
||||
bool
|
||||
nsGlobalWindow::CanSetProperty(const char *aPrefName)
|
||||
@ -7608,13 +7619,9 @@ nsGlobalWindow::RevisePopupAbuseLevel(PopupControlState aControl)
|
||||
return abuse;
|
||||
}
|
||||
|
||||
/* If a window open is blocked, fire the appropriate DOM events.
|
||||
aBlocked signifies we just blocked a popup.
|
||||
aWindow signifies we just opened what is probably a popup.
|
||||
*/
|
||||
/* If a window open is blocked, fire the appropriate DOM events. */
|
||||
void
|
||||
nsGlobalWindow::FireAbuseEvents(bool aBlocked, bool aWindow,
|
||||
const nsAString &aPopupURL,
|
||||
nsGlobalWindow::FireAbuseEvents(const nsAString &aPopupURL,
|
||||
const nsAString &aPopupWindowName,
|
||||
const nsAString &aPopupWindowFeatures)
|
||||
{
|
||||
@ -7645,13 +7652,8 @@ nsGlobalWindow::FireAbuseEvents(bool aBlocked, bool aWindow,
|
||||
ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), 0, baseURL,
|
||||
getter_AddRefs(popupURI));
|
||||
|
||||
// fire an event chock full of informative URIs
|
||||
if (aBlocked) {
|
||||
FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
|
||||
aPopupWindowFeatures);
|
||||
}
|
||||
if (aWindow)
|
||||
FirePopupWindowEvent(topDoc);
|
||||
FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
|
||||
aPopupWindowFeatures);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIDOMWindow>
|
||||
@ -11333,8 +11335,24 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
nsIPrincipal::APP_STATUS_INSTALLED;
|
||||
}
|
||||
|
||||
bool forceNoOpener = false;
|
||||
// Unlike other window flags, "noopener" comes from splitting on commas with
|
||||
// HTML whitespace trimming...
|
||||
nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
|
||||
aOptions, ',');
|
||||
while (tok.hasMoreTokens()) {
|
||||
if (tok.nextToken().EqualsLiteral("noopener")) {
|
||||
forceNoOpener = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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() &&
|
||||
!isApp && !aDialog && !WindowExists(aName, !aCalledNoScript);
|
||||
!isApp && !aDialog && !WindowExists(aName, forceNoOpener, !aCalledNoScript);
|
||||
|
||||
// Note: it's very important that this be an nsXPIDLCString, since we want
|
||||
// .get() on it to return nullptr until we write stuff to it. The window
|
||||
@ -11377,7 +11395,7 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
}
|
||||
}
|
||||
|
||||
FireAbuseEvents(true, false, aUrl, aName, aOptions);
|
||||
FireAbuseEvents(aUrl, aName, aOptions);
|
||||
return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
@ -11397,6 +11415,13 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
|
||||
NS_ENSURE_STATE(pwwatch);
|
||||
|
||||
MOZ_ASSERT_IF(checkForPopup, abuseLevel < openAbused);
|
||||
// At this point we should know for a fact that if checkForPopup then
|
||||
// abuseLevel < openAbused, so we could just check for abuseLevel ==
|
||||
// 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 >= openControlled);
|
||||
|
||||
{
|
||||
// Reset popup state while opening a window to prevent the
|
||||
// current state from being active the whole time a modal
|
||||
@ -11409,6 +11434,8 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
rv = pwwatch->OpenWindow2(this, url.get(), name_ptr, options_ptr,
|
||||
/* aCalledFromScript = */ true,
|
||||
aDialog, aNavigate, nullptr, argv,
|
||||
isPopupSpamWindow,
|
||||
forceNoOpener,
|
||||
getter_AddRefs(domReturn));
|
||||
} else {
|
||||
// Force a system caller here so that the window watcher won't screw us
|
||||
@ -11425,10 +11452,11 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
nojsapi.emplace();
|
||||
}
|
||||
|
||||
|
||||
rv = pwwatch->OpenWindow2(this, url.get(), name_ptr, options_ptr,
|
||||
/* aCalledFromScript = */ false,
|
||||
aDialog, aNavigate, nullptr, aExtraArgument,
|
||||
isPopupSpamWindow,
|
||||
forceNoOpener,
|
||||
getter_AddRefs(domReturn));
|
||||
|
||||
}
|
||||
@ -11463,18 +11491,6 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
}
|
||||
}
|
||||
|
||||
if (checkForPopup) {
|
||||
if (abuseLevel >= openControlled) {
|
||||
nsGlobalWindow *opened = static_cast<nsGlobalWindow *>(*aReturn);
|
||||
if (!opened->IsPopupSpamWindow()) {
|
||||
opened->SetPopupSpamWindow(true);
|
||||
++gOpenPopupSpamCount;
|
||||
}
|
||||
}
|
||||
if (abuseLevel >= openAbused)
|
||||
FireAbuseEvents(false, true, aUrl, aName, aOptions);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -1321,6 +1321,7 @@ protected:
|
||||
// Get the parent, returns null if this is a toplevel window
|
||||
nsIDOMWindow* GetParentInternal();
|
||||
|
||||
public:
|
||||
// popup tracking
|
||||
bool IsPopupSpamWindow()
|
||||
{
|
||||
@ -1331,17 +1332,10 @@ protected:
|
||||
return GetOuterWindowInternal()->mIsPopupSpam;
|
||||
}
|
||||
|
||||
void SetPopupSpamWindow(bool aPopup)
|
||||
{
|
||||
if (IsInnerWindow() && !mOuterWindow) {
|
||||
NS_ERROR("SetPopupSpamWindow() called on inner window w/o an outer!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GetOuterWindowInternal()->mIsPopupSpam = aPopup;
|
||||
}
|
||||
// Outer windows only.
|
||||
void SetIsPopupSpamWindow(bool aIsPopupSpam);
|
||||
|
||||
protected:
|
||||
// Window Control Functions
|
||||
|
||||
// Outer windows only.
|
||||
@ -1471,8 +1465,7 @@ public:
|
||||
|
||||
bool PopupWhitelisted();
|
||||
PopupControlState RevisePopupAbuseLevel(PopupControlState);
|
||||
void FireAbuseEvents(bool aBlocked, bool aWindow,
|
||||
const nsAString &aPopupURL,
|
||||
void FireAbuseEvents(const nsAString &aPopupURL,
|
||||
const nsAString &aPopupWindowName,
|
||||
const nsAString &aPopupWindowFeatures);
|
||||
void FireOfflineStatusEventIfChanged();
|
||||
@ -1538,7 +1531,8 @@ public:
|
||||
// If aLookForCallerOnJSStack is true, this method will look at the JS stack
|
||||
// to determine who the caller is. If it's false, it'll use |this| as the
|
||||
// caller.
|
||||
bool WindowExists(const nsAString& aName, bool aLookForCallerOnJSStack);
|
||||
bool WindowExists(const nsAString& aName, bool aForceNoOpener,
|
||||
bool aLookForCallerOnJSStack);
|
||||
|
||||
already_AddRefed<nsIWidget> GetMainWidget();
|
||||
nsIWidget* GetNearestWidget() const;
|
||||
|
@ -5576,8 +5576,13 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
|
||||
const char* name = aName.IsVoid() ? nullptr : NS_ConvertUTF16toUTF8(aName).get();
|
||||
const char* features = aFeatures.IsVoid() ? nullptr : aFeatures.get();
|
||||
|
||||
#if(0)
|
||||
// Needs update for issue 522
|
||||
*aResult = pwwatch->OpenWindow2(parent, uri, name, features, aCalledFromJS,
|
||||
false, false, thisTabParent, nullptr, getter_AddRefs(window));
|
||||
#else
|
||||
MOZ_ASSERT(0);
|
||||
#endif
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(*aResult))) {
|
||||
return true;
|
||||
|
@ -608,6 +608,12 @@ private:
|
||||
nullptr,
|
||||
nullptr,
|
||||
false, false, true, nullptr, nullptr,
|
||||
// Not a spammy popup; we got permission, we swear!
|
||||
/* aIsPopupSpam = */ false,
|
||||
// Don't force noopener. We're not passing in an
|
||||
// opener anyway, and we _do_ want the returned
|
||||
// window.
|
||||
/* aForceNoOpener = */ false,
|
||||
getter_AddRefs(newWindow));
|
||||
nsCOMPtr<nsPIDOMWindow> pwindow = do_QueryInterface(newWindow);
|
||||
pwindow.forget(aWindow);
|
||||
|
@ -56,6 +56,12 @@ interface nsPIWindowWatcher : nsISupports
|
||||
nsITabParent is a remote tab belonging to aParent. Can
|
||||
be nullptr if this window is not being opened from a tab.
|
||||
@param aArgs Window argument
|
||||
@param aIsPopupSpam true if the window is a popup spam window; used for
|
||||
popup blocker internals.
|
||||
@param aForceNoOpener If true, force noopener behavior. This means not
|
||||
looking for existing windows with the given name,
|
||||
not setting an opener on the newly opened window,
|
||||
and returning null from this method.
|
||||
@return the new window
|
||||
|
||||
@note This method may examine the JS context stack for purposes of
|
||||
@ -70,7 +76,9 @@ interface nsPIWindowWatcher : nsISupports
|
||||
in string aName, in string aFeatures,
|
||||
in boolean aCalledFromScript, in boolean aDialog,
|
||||
in boolean aNavigate, in nsITabParent aOpeningTab,
|
||||
in nsISupports aArgs);
|
||||
in nsISupports aArgs,
|
||||
in boolean aIsPopupSpam,
|
||||
in boolean aForceNoOpener);
|
||||
|
||||
/**
|
||||
* Find a named docshell tree item amongst all windows registered
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "nsJSUtils.h"
|
||||
#include "plstr.h"
|
||||
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsIBaseWindow.h"
|
||||
#include "nsIBrowserDOMWindow.h"
|
||||
#include "nsIDocShell.h"
|
||||
@ -366,7 +367,10 @@ nsWindowWatcher::OpenWindow(nsIDOMWindow* aParent,
|
||||
|
||||
return OpenWindowInternal(aParent, aUrl, aName, aFeatures,
|
||||
/* calledFromJS = */ false, dialog,
|
||||
/* navigate = */ true, nullptr, argv, aResult);
|
||||
/* navigate = */ true, nullptr, argv,
|
||||
/* aIsPopupSpam = */ false,
|
||||
/* aForceNoOpener = */ false,
|
||||
aResult);
|
||||
}
|
||||
|
||||
struct SizeSpec
|
||||
@ -424,6 +428,8 @@ nsWindowWatcher::OpenWindow2(nsIDOMWindow* aParent,
|
||||
bool aNavigate,
|
||||
nsITabParent* aOpeningTab,
|
||||
nsISupports* aArguments,
|
||||
bool aIsPopupSpam,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow** aResult)
|
||||
{
|
||||
nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments);
|
||||
@ -443,7 +449,10 @@ nsWindowWatcher::OpenWindow2(nsIDOMWindow* aParent,
|
||||
|
||||
return OpenWindowInternal(aParent, aUrl, aName, aFeatures,
|
||||
aCalledFromScript, dialog,
|
||||
aNavigate, aOpeningTab, argv, aResult);
|
||||
aNavigate, aOpeningTab, argv,
|
||||
aIsPopupSpam,
|
||||
aForceNoOpener,
|
||||
aResult);
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -456,6 +465,8 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
|
||||
bool aNavigate,
|
||||
nsITabParent* aOpeningTab,
|
||||
nsIArray* aArgv,
|
||||
bool aIsPopupSpam,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow** aResult)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
@ -531,7 +542,8 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
|
||||
// know or care about names - unless we're opening named windows from chrome.
|
||||
if (!aOpeningTab) {
|
||||
// try to find an extant window with the given name
|
||||
nsCOMPtr<nsIDOMWindow> foundWindow = SafeGetWindowByName(name, aParent);
|
||||
nsCOMPtr<nsIDOMWindow> foundWindow =
|
||||
SafeGetWindowByName(name, aForceNoOpener, aParent);
|
||||
GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem));
|
||||
}
|
||||
|
||||
@ -828,7 +840,8 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
|
||||
}
|
||||
}
|
||||
|
||||
rv = ReadyOpenedDocShellItem(newDocShellItem, aParent, windowIsNew, aResult);
|
||||
rv = ReadyOpenedDocShellItem(newDocShellItem, aParent, windowIsNew,
|
||||
aForceNoOpener, aResult);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
@ -900,6 +913,16 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
|
||||
// SetInitialPrincipalToSubject is safe to call multiple times.
|
||||
if (newWindow) {
|
||||
newWindow->SetInitialPrincipalToSubject();
|
||||
if (aIsPopupSpam) {
|
||||
nsGlobalWindow* globalWin = static_cast<nsGlobalWindow*>(newWindow.get());
|
||||
MOZ_ASSERT(!globalWin->IsPopupSpamWindow(),
|
||||
"Who marked it as popup spam already???");
|
||||
if (!globalWin->IsPopupSpamWindow()) { // Make sure we don't mess up our
|
||||
// counter even if the above
|
||||
// assert fails.
|
||||
globalWin->SetIsPopupSpamWindow(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1069,6 +1092,10 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
|
||||
}
|
||||
}
|
||||
|
||||
if (aForceNoOpener && windowIsNew) {
|
||||
NS_RELEASE(*aResult);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1857,8 +1884,18 @@ nsWindowWatcher::GetCallerTreeItem(nsIDocShellTreeItem* aParentItem)
|
||||
|
||||
nsPIDOMWindow*
|
||||
nsWindowWatcher::SafeGetWindowByName(const nsAString& aName,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow* aCurrentWindow)
|
||||
{
|
||||
if (aForceNoOpener) {
|
||||
if (!aName.LowerCaseEqualsLiteral("_self") &&
|
||||
!aName.LowerCaseEqualsLiteral("_top") &&
|
||||
!aName.LowerCaseEqualsLiteral("_parent")) {
|
||||
// Ignore all other names in the noopener case.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocShellTreeItem> startItem;
|
||||
GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem));
|
||||
|
||||
@ -1887,6 +1924,7 @@ nsresult
|
||||
nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem,
|
||||
nsIDOMWindow* aParent,
|
||||
bool aWindowIsNew,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow** aOpenedWindow)
|
||||
{
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
@ -1897,7 +1935,9 @@ nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem,
|
||||
nsCOMPtr<nsPIDOMWindow> piOpenedWindow = aOpenedItem->GetWindow();
|
||||
if (piOpenedWindow) {
|
||||
if (aParent) {
|
||||
piOpenedWindow->SetOpenerWindow(aParent, aWindowIsNew); // damnit
|
||||
if (!aForceNoOpener) {
|
||||
piOpenedWindow->SetOpenerWindow(aParent, aWindowIsNew); // damnit
|
||||
}
|
||||
|
||||
if (aWindowIsNew) {
|
||||
#ifdef DEBUG
|
||||
|
@ -70,7 +70,9 @@ protected:
|
||||
|
||||
// Unlike GetWindowByName this will look for a caller on the JS
|
||||
// stack, and then fall back on aCurrentWindow if it can't find one.
|
||||
// It also knows to not look for things if aForceNoOpener is set.
|
||||
nsPIDOMWindow* SafeGetWindowByName(const nsAString& aName,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow* aCurrentWindow);
|
||||
|
||||
// Just like OpenWindowJS, but knows whether it got called via OpenWindowJS
|
||||
@ -84,6 +86,8 @@ protected:
|
||||
bool aNavigate,
|
||||
nsITabParent* aOpeningTab,
|
||||
nsIArray* aArgv,
|
||||
bool aIsPopupSpam,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow** aResult);
|
||||
|
||||
static nsresult URIfromURL(const char* aURL,
|
||||
@ -105,6 +109,7 @@ protected:
|
||||
static nsresult ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem,
|
||||
nsIDOMWindow* aParent,
|
||||
bool aWindowIsNew,
|
||||
bool aForceNoOpener,
|
||||
nsIDOMWindow** aOpenedWindow);
|
||||
static void SizeOpenedDocShellItem(nsIDocShellTreeItem* aDocShellItem,
|
||||
nsIDOMWindow* aParent,
|
||||
|
Loading…
Reference in New Issue
Block a user