diff --git a/content/xml/content/src/nsXMLElement.cpp b/content/xml/content/src/nsXMLElement.cpp index 4ea90297872..4f22ad72eb4 100644 --- a/content/xml/content/src/nsXMLElement.cpp +++ b/content/xml/content/src/nsXMLElement.cpp @@ -62,7 +62,6 @@ #include "nsIPresShell.h" #include "nsGUIEvent.h" #include "nsPresContext.h" -#include "nsIBrowserDOMWindow.h" #include "nsIDOMCSSStyleDeclaration.h" #include "nsIDOMViewCSS.h" #include "nsIXBLService.h" @@ -221,17 +220,10 @@ nsXMLElement::MaybeTriggerAutoLink(nsIDocShell *aShell) // XXX Should probably do this using atoms if (value.EqualsLiteral("new")) { - if (nsContentUtils::GetBoolPref("dom.disable_open_during_load")) { - // disabling open during load - - return NS_OK; - } - - if (nsContentUtils::GetIntPref("browser.link.open_newwindow", - nsIBrowserDOMWindow::OPEN_NEWWINDOW) == - nsIBrowserDOMWindow::OPEN_NEWWINDOW) { - verb = eLinkVerb_New; - } + // We should just act like an HTML link with target="_blank" and if + // someone diverts or blocks those, that's fine with us. We don't + // care. + verb = eLinkVerb_New; } else if (value.EqualsLiteral("replace")) { // We want to actually stop processing the current document now. // We do this by returning the correct value so that the one @@ -307,11 +299,7 @@ nsXMLElement::HandleDOMEvent(nsPresContext* aPresContext, // XXX Should probably do this using atoms if (show.EqualsLiteral("new")) { - if (nsContentUtils::GetIntPref("browser.link.open_newwindow", - nsIBrowserDOMWindow::OPEN_NEWWINDOW) == - nsIBrowserDOMWindow::OPEN_NEWWINDOW) { - verb = eLinkVerb_New; - } + verb = eLinkVerb_New; } else if (show.EqualsLiteral("replace")) { verb = eLinkVerb_Replace; } else if (show.EqualsLiteral("embed")) { diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index d811c6cb7f2..104852064ae 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -1097,160 +1097,6 @@ nsDocShell::ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem, documentDomainSet); } -nsresult nsDocShell::FindTarget(const PRUnichar *aWindowTarget, - PRBool *aIsNewWindow, - nsIDocShell **aResult) -{ - nsresult rv = NS_OK; - - *aResult = nsnull; - *aIsNewWindow = PR_FALSE; - - // Try to locate the target window... - nsCOMPtr treeItem; - FindItemWithName(aWindowTarget, nsnull, this, getter_AddRefs(treeItem)); - - PRInt32 linkPref = nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW; - // XXXbz this should live inside FindItemWithName, I think... - if (!treeItem) { - mPrefs->GetIntPref("browser.link.open_newwindow", &linkPref); - if (linkPref == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { - // force new window to go to _top - FindItemWithName(NS_LITERAL_STRING("_top").get(), - nsnull, this, getter_AddRefs(treeItem)); - NS_ASSERTION(treeItem, - "We better get a treeitem when asking for '_top' " - "with |this| as the original requestor"); - } - } - - if (!treeItem) - { - /// XXXbz this should really happen in FindItemWithName too, I think... - nsCOMPtr newWindow; - nsCOMPtr parentWindow; - - // This DocShell is the parent window - parentWindow = do_GetInterface(GetAsSupports(this)); - if (!parentWindow) { - NS_ASSERTION(0, "Can't get nsIDOMWindowInternal from nsDocShell!"); - return NS_ERROR_FAILURE; - } - - if (linkPref == nsIBrowserDOMWindow::OPEN_NEWTAB) { - - // is it a popup? - - PRBool allowTab = PR_TRUE; - nsCOMPtr pWindow = do_QueryInterface(mScriptGlobal); - if (pWindow) { - // skip the window search-by-name of GetOpenAllow - // by using _self. we don't care about that at this point. - OpenAllowValue allow = pWindow->GetOpenAllow( - NS_LITERAL_STRING("_self")); - if (allow == allowNot || allow == allowSelf) - allowTab = PR_FALSE; - } - - // try to get our tab-opening interface - - if (allowTab) { - nsCOMPtr bwin; - - nsCOMPtr rootItem; - GetRootTreeItem(getter_AddRefs(rootItem)); - nsCOMPtr rootWin(do_GetInterface(rootItem)); - nsCOMPtr chromeWin( - do_QueryInterface(rootWin)); - if (chromeWin) - chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); - - // open a new tab - if (bwin) { - rv = bwin->OpenURI(0, 0, nsIBrowserDOMWindow::OPEN_NEWTAB, - nsIBrowserDOMWindow::OPEN_NEW, - getter_AddRefs(newWindow)); - - nsCOMPtr newPIWindow = - do_GetInterface(newWindow); - if (newPIWindow) - newPIWindow->SetOpenerWindow(parentWindow); - } - } - // else fall through to the normal Open method, from which - // the appropriate measures will be taken when the popup fails - } - - if (!newWindow) { - nsAutoString name(aWindowTarget); - // XXXbz this should be handled somewhere else.... and in fact, it - // may be safe to just pass those through here. Check. - if (name.LowerCaseEqualsLiteral("_blank") || - name.LowerCaseEqualsLiteral("_new")) { - name.Truncate(); - } - rv = parentWindow->Open(EmptyString(), // URL to load - name, // Window name - EmptyString(), // Window features - getter_AddRefs(newWindow)); - } - if (NS_FAILED(rv)) return rv; - - { - // Get the DocShell from the new window... - nsCOMPtr piwindow(do_QueryInterface(newWindow)); - - // *aResult will be AddRef()'ed below... - *aResult = piwindow->GetDocShell(); - } - - // If all went well, indicate that a new window has been created. - if (*aResult) { - NS_ADDREF(*aResult); - - *aIsNewWindow = PR_TRUE; - - // if we just open a new window for this link, charset from current docshell - // should be kept, as what we did in js openNewWindowWith(url) - nsCOMPtr muCV, target_muCV; - nsCOMPtr cv, target_cv; - this->GetContentViewer(getter_AddRefs(cv)); - (*aResult)->GetContentViewer(getter_AddRefs(target_cv)); - if (cv && target_cv) { - muCV = do_QueryInterface(cv); - target_muCV = do_QueryInterface(target_cv); - if (muCV && target_muCV) { - nsCAutoString defaultCharset; - nsCAutoString prevDocCharset; - rv = muCV->GetDefaultCharacterSet(defaultCharset); - if(NS_SUCCEEDED(rv)) { - target_muCV->SetDefaultCharacterSet(defaultCharset); - } - rv = muCV->GetPrevDocCharacterSet(prevDocCharset); - if(NS_SUCCEEDED(rv)) { - target_muCV->SetPrevDocCharacterSet(prevDocCharset); - } - } - } - } - - return rv; - } - else - { - if (treeItem) - { - NS_ASSERTION(!*aResult, "aResult should be null if treeItem is set!"); - treeItem->QueryInterface(NS_GET_IID(nsIDocShell), (void **)aResult); - } - else - { - NS_IF_ADDREF(*aResult); - } - } - return NS_OK; -} - NS_IMETHODIMP nsDocShell::GetEldestPresContext(nsPresContext** aPresContext) { @@ -6336,68 +6182,38 @@ nsDocShell::InternalLoad(nsIURI * aURI, // load to it... // if (aWindowTarget && *aWindowTarget) { - PRBool bIsNewWindow; - nsCOMPtr targetDocShell; - nsAutoString name(aWindowTarget); - - // - // This is a hack to prevent top-level windows from ever being - // created. It really doesn't belong here, but until there is a - // way for embeddors to get involved in window targeting, this is - // as good a place as any... - // XXXbz no, it's not.... fixme!!!! - // - PRInt32 linkPref = nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW; - mPrefs->GetIntPref("browser.link.open_newwindow", &linkPref); - if (linkPref == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { - PRBool bIsChromeOrResource = PR_FALSE; - if (mCurrentURI) - mCurrentURI->SchemeIs("chrome", &bIsChromeOrResource); - if (!bIsChromeOrResource) { - aURI->SchemeIs("chrome", &bIsChromeOrResource); - if (!bIsChromeOrResource) { - aURI->SchemeIs("resource", &bIsChromeOrResource); - } - } - if (!bIsChromeOrResource) { - if (name.LowerCaseEqualsLiteral("_blank") || - name.LowerCaseEqualsLiteral("_new")) { - name.AssignLiteral("_top"); - } - // _main is an IE target which should be case-insensitive but isn't - // see bug 217886 for details - else if (!name.LowerCaseEqualsLiteral("_parent") && - !name.LowerCaseEqualsLiteral("_self") && - !name.LowerCaseEqualsLiteral("_content") && - !name.EqualsLiteral("_main")) { - nsCOMPtr targetTreeItem; - FindItemWithName(name.get(), - nsnull, - this, - getter_AddRefs(targetTreeItem)); - if (targetTreeItem) - targetDocShell = do_QueryInterface(targetTreeItem); - else - name.AssignLiteral("_top"); - } - } - } - - // // Locate the target DocShell. // This may involve creating a new toplevel window - if necessary. // - if (!targetDocShell) { - rv = FindTarget(name.get(), &bIsNewWindow, - getter_AddRefs(targetDocShell)); - } + nsCOMPtr targetItem; + FindItemWithName(aWindowTarget, nsnull, this, + getter_AddRefs(targetItem)); - NS_ASSERTION(targetDocShell, "No Target docshell could be found!"); + nsCOMPtr targetDocShell = do_QueryInterface(targetItem); + + PRBool isNewWindow = PR_FALSE; + if (!targetDocShell) { + nsCOMPtr win = + do_GetInterface(GetAsSupports(this)); + NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE); + + isNewWindow = PR_TRUE; + nsDependentString name(aWindowTarget); + nsCOMPtr newWin; + rv = win->Open(EmptyString(), // URL to load + name, // window name + EmptyString(), // Features + getter_AddRefs(newWin)); + + nsCOMPtr webNav = do_GetInterface(newWin); + targetDocShell = do_QueryInterface(webNav); + } + // // Transfer the load to the target DocShell... Pass nsnull as the // window target name from to prevent recursive retargeting! // - if (targetDocShell) { + if (NS_SUCCEEDED(rv) && targetDocShell) { rv = targetDocShell->InternalLoad(aURI, aReferrer, owner, @@ -6412,7 +6228,8 @@ nsDocShell::InternalLoad(nsIURI * aURI, aDocShell, aRequest); if (rv == NS_ERROR_NO_CONTENT) { - if (bIsNewWindow) { + // XXXbz except we never reach this code! + if (isNewWindow) { // // At this point, a new window has been created, but the // URI did not have any data associated with it... @@ -6420,18 +6237,10 @@ nsDocShell::InternalLoad(nsIURI * aURI, // So, the best we can do, is to tear down the new window // that was just created! // - nsCOMPtr treeItem; - nsCOMPtr treeOwner; - - treeItem = do_QueryInterface(targetDocShell); - treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); - if (treeOwner) { - nsCOMPtr treeOwnerAsWin; - - treeOwnerAsWin = do_QueryInterface(treeOwner); - if (treeOwnerAsWin) { - treeOwnerAsWin->Destroy(); - } + nsCOMPtr domWin = + do_GetInterface(targetDocShell); + if (domWin) { + domWin->Close(); } } // @@ -6442,12 +6251,16 @@ nsDocShell::InternalLoad(nsIURI * aURI, // rv = NS_OK; } - else if (bIsNewWindow) { + else if (isNewWindow) { // XXX: Once new windows are created hidden, the new // window will need to be made visible... For now, // do nothing. } } + + // Else we ran out of memory, or were a popup and got blocked, + // or something. + return rv; } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index e0f8ce1ced6..2a1b7da5df6 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -439,10 +439,6 @@ protected: return t_sec; } - nsresult FindTarget(const PRUnichar *aTargetName, - PRBool *aIsNewWindow, - nsIDocShell **aResult); - PRBool IsFrame(); // diff --git a/docshell/base/nsWebShell.cpp b/docshell/base/nsWebShell.cpp index 06183dd8ce8..90e019ff7f9 100644 --- a/docshell/base/nsWebShell.cpp +++ b/docshell/base/nsWebShell.cpp @@ -768,6 +768,7 @@ nsWebShell::OnLinkClickSync(nsIContent *aContent, nsresult rv; switch(aVerb) { case eLinkVerb_New: + NS_ASSERTION(target.IsEmpty(), "Losing window name information"); target.AssignLiteral("_blank"); // Fall into replace case case eLinkVerb_Undefined: diff --git a/dom/public/base/nsPIDOMWindow.h b/dom/public/base/nsPIDOMWindow.h index 9c73fa4379a..f8b8426bc07 100644 --- a/dom/public/base/nsPIDOMWindow.h +++ b/dom/public/base/nsPIDOMWindow.h @@ -66,12 +66,11 @@ enum PopupControlState { enum OpenAllowValue { allowNot = 0, // the window opening is denied allowNoAbuse, // allowed: not a popup - allowSelf, // allowed: it's the same window (_self, _top, et.al.) - allowExtant, // allowed: an already open window allowWhitelisted // allowed: it's whitelisted or popup blocking is disabled }; class nsIDocShell; +class nsIDocShellTreeItem; class nsIFocusController; class nsIDocument; struct nsTimeout; @@ -258,6 +257,11 @@ public: PRBool aForce) const = 0; virtual void PopPopupControlState(PopupControlState state) const = 0; virtual PopupControlState GetPopupControlState() const = 0; + + // GetOpenAllow must not be called on a window that no longer has a docshell + // This function is deprecated. It will assume that there is no existing + // window with name aName for purposes of its answer. Expect this function + // to get removed soon! virtual OpenAllowValue GetOpenAllow(const nsAString &aName) = 0; // Returns an object containing the window's state. This also suspends diff --git a/dom/public/idl/base/nsIBrowserDOMWindow.idl b/dom/public/idl/base/nsIBrowserDOMWindow.idl index d770d5f0ae2..2b6288cec7c 100644 --- a/dom/public/idl/base/nsIBrowserDOMWindow.idl +++ b/dom/public/idl/base/nsIBrowserDOMWindow.idl @@ -44,26 +44,50 @@ interface nsIURI; /** * The C++ source has access to the browser script source through - * nsIBrowserWindow. It is intended to be attached to the chrome - * DOMWindow of a browser window. A DOMWindow that does not happen to - * be a browser chrome window will simply have no access to any such + * nsIBrowserDOMWindow. It is intended to be attached to the chrome DOMWindow + * of a toplevel browser window (a XUL window). A DOMWindow that does not + * happen to be a browser chrome window will simply have no access to any such * interface. */ interface nsIBrowserDOMWindow : nsISupports { /** - * values for openURI's aWhere parameter + * Values for openURI's aWhere parameter. + */ + /** + * Do whatever the default is based on application state, user preferences, + * and the value of the aContext parameter to openURI. */ const short OPEN_DEFAULTWINDOW = 0; + /** + * Open in the "current window". If aOpener is provided, this should be the + * top window in aOpener's window hierarchy, but exact behavior is + * application-dependent. If aOpener is not provided, it's up to the + * application to decide what constitutes a "current window". + */ const short OPEN_CURRENTWINDOW = 1; + /** + * Open in a new window. + */ const short OPEN_NEWWINDOW = 2; + /** + * Open in a new content tab in the toplevel browser window corresponding to + * this nsIBrowserDOMWindow. + */ const short OPEN_NEWTAB = 3; /** - * values for openURI's aContext parameter + * Values for openURI's aContext parameter. These affect the behavior of + * OPEN_DEFAULTWINDOW. */ - const short OPEN_EXTERNAL = 1; // external link - const short OPEN_NEW = 2; // internal open new window + /** + * external link (load request from another application, xremote, etc). + */ + const short OPEN_EXTERNAL = 1; + /** + * internal open new window + */ + const short OPEN_NEW = 2; /** * Load a URI @@ -80,7 +104,7 @@ interface nsIBrowserDOMWindow : nsISupports /** * @param aWindow the window to test. * @return whether the window is the main content window for any - * currently open tab. + * currently open tab in this toplevel browser window. */ boolean isTabContentWindow(in nsIDOMWindow aWindow); }; diff --git a/dom/public/idl/base/nsIDOMJSWindow.idl b/dom/public/idl/base/nsIDOMJSWindow.idl index 1f61dea2f3a..c0821e6bbc8 100644 --- a/dom/public/idl/base/nsIDOMJSWindow.idl +++ b/dom/public/idl/base/nsIDOMJSWindow.idl @@ -77,8 +77,16 @@ interface nsIDOMJSWindow : nsISupports */ DOMString prompt(); - // This is the script version of nsIDOMWindowInternal::open() that - // takes 3 optional arguments + /** + * These are the scriptable versions of nsIDOMWindowInternal::open() and + * nsIDOMWindowInternal::openDialog() that take 3 optional arguments. Unlike + * the nsIDOMWindowInternal methods, these methods assume that they are + * called from JavaScript and hence will look on the JS context stack to + * determine the caller and hence correct security context for doing their + * search for an existing named window. Also, these methods will set the + * default charset on the newly opened window based on the current document + * charset in the caller. + */ nsIDOMWindow open(); nsIDOMWindow openDialog(); diff --git a/dom/public/idl/base/nsIDOMWindowInternal.idl b/dom/public/idl/base/nsIDOMWindowInternal.idl index 01989d93b9c..78de77f72e9 100644 --- a/dom/public/idl/base/nsIDOMWindowInternal.idl +++ b/dom/public/idl/base/nsIDOMWindowInternal.idl @@ -150,6 +150,13 @@ interface nsIDOMWindowInternal : nsIDOMWindow2 //[noscript] long setInterval(/* in function, // in unsigned long timeout */); + /** + * Open a new window with this one as the parent. This method will + * NOT examine the JS stack for purposes of determining a caller. + * This window will be used for security checks during the search by + * name and the default character set on the newly opened window + * will just be the default character set of this window. + */ [noscript] nsIDOMWindow open(in DOMString url, in DOMString name, in DOMString options); // This method works like open except that aExtraArgument gets diff --git a/dom/src/base/nsGlobalWindow.cpp b/dom/src/base/nsGlobalWindow.cpp index 196b8e82b64..11fc42adb27 100644 --- a/dom/src/base/nsGlobalWindow.cpp +++ b/dom/src/base/nsGlobalWindow.cpp @@ -435,7 +435,7 @@ nsGlobalWindow::CleanUp() } mChromeEventHandler = nsnull; // Forces Release - if (IsPopupSpamWindow()) { + if (IsOuterWindow() && IsPopupSpamWindow()) { SetPopupSpamWindow(PR_FALSE); --gOpenPopupSpamCount; } @@ -2960,43 +2960,29 @@ GetCallerDocShellTreeItem() } PRBool -nsGlobalWindow::WindowExists(const nsAString& aName) +nsGlobalWindow::WindowExists(const nsAString& aName, + PRBool aLookForCallerOnJSStack) { - nsCOMPtr caller = GetCallerDocShellTreeItem(); - PRBool foundWindow = PR_FALSE; + NS_PRECONDITION(IsOuterWindow(), "Must be outer window"); + NS_PRECONDITION(mDocShell, "Must have docshell"); + + nsCOMPtr caller; + if (aLookForCallerOnJSStack) { + caller = GetCallerDocShellTreeItem(); + } + + nsCOMPtr docShell = do_QueryInterface(mDocShell); + NS_ASSERTION(docShell, + "Docshell doesn't implement nsIDocShellTreeItem?"); if (!caller) { - // If we can't reach a caller, try to use our own docshell - caller = do_QueryInterface(GetDocShell()); + caller = docShell; } - nsCOMPtr docShell = - do_QueryInterface(GetDocShell()); - - if (docShell) { - nsCOMPtr namedItem; - - docShell->FindItemWithName(PromiseFlatString(aName).get(), nsnull, caller, - getter_AddRefs(namedItem)); - - foundWindow = !!namedItem; - } else { - // No caller reachable and we don't have a docshell any more. Fall - // back to using the windowwatcher service to find any window by - // name. - - nsCOMPtr wwatch = - do_GetService(NS_WINDOWWATCHER_CONTRACTID); - if (wwatch) { - nsCOMPtr namedWindow; - wwatch->GetWindowByName(PromiseFlatString(aName).get(), nsnull, - getter_AddRefs(namedWindow)); - - foundWindow = !!namedWindow; - } - } - - return foundWindow; + nsCOMPtr namedItem; + docShell->FindItemWithName(PromiseFlatString(aName).get(), nsnull, caller, + getter_AddRefs(namedItem)); + return namedItem != nsnull; } already_AddRefed @@ -4072,15 +4058,16 @@ nsGlobalWindow::CheckForAbusePoint() { FORWARD_TO_OUTER(CheckForAbusePoint, (), openAbused); + NS_ASSERTION(mDocShell, "Must have docshell"); + nsCOMPtr item(do_QueryInterface(mDocShell)); - if (item) { - PRInt32 type = nsIDocShellTreeItem::typeChrome; + NS_ASSERTION(item, "Docshell doesn't implenent nsIDocShellTreeItem?"); - item->GetItemType(&type); - if (type != nsIDocShellTreeItem::typeContent) - return openAllowed; - } + PRInt32 type = nsIDocShellTreeItem::typeChrome; + item->GetItemType(&type); + if (type != nsIDocShellTreeItem::typeContent) + return openAllowed; // level of abuse we've detected, initialized to the current popup // state @@ -4097,37 +4084,21 @@ nsGlobalWindow::CheckForAbusePoint() } /* Allow or deny a window open based on whether popups are suppressed. - A popup generally will be allowed if it's from a white-listed domain, - or if its target is an extant window. + A popup generally will be allowed if it's from a white-listed domain. Returns a value from the CheckOpenAllow enum. */ OpenAllowValue -nsGlobalWindow::CheckOpenAllow(PopupControlState aAbuseLevel, - const nsAString &aName) +nsGlobalWindow::CheckOpenAllow(PopupControlState aAbuseLevel) { + NS_PRECONDITION(GetDocShell(), "Must have docshell"); + OpenAllowValue allowWindow = allowNoAbuse; // (also used for openControlled) if (aAbuseLevel >= openAbused) { allowWindow = allowNot; // However it might still not be blocked. - if (aAbuseLevel == openAbused && !IsPopupBlocked(mDocument)) + if (aAbuseLevel == openAbused && !IsPopupBlocked(mDocument)) { allowWindow = allowWhitelisted; - else { - // Special case items that don't actually open new windows. - if (!aName.IsEmpty()) { - // _main is an IE target which should be case-insensitive but isn't - // see bug 217886 for details - if (aName.LowerCaseEqualsLiteral("_top") || - aName.LowerCaseEqualsLiteral("_self") || - aName.LowerCaseEqualsLiteral("_content") || - aName.EqualsLiteral("_main")) - allowWindow = allowSelf; - else { - if (WindowExists(aName)) { - allowWindow = allowExtant; - } - } - } } } @@ -4137,7 +4108,8 @@ nsGlobalWindow::CheckOpenAllow(PopupControlState aAbuseLevel, OpenAllowValue nsGlobalWindow::GetOpenAllow(const nsAString &aName) { - return CheckOpenAllow(CheckForAbusePoint(), aName); + NS_ENSURE_TRUE(GetDocShell(), allowNot); + return CheckOpenAllow(CheckForAbusePoint()); } /* If a window open is blocked, fire the appropriate DOM events. @@ -4211,29 +4183,12 @@ NS_IMETHODIMP nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions, nsIDOMWindow **_retval) { - nsresult rv; - - PopupControlState abuseLevel = CheckForAbusePoint(); - OpenAllowValue allowReason = CheckOpenAllow(abuseLevel, aName); - if (allowReason == allowNot) { - FireAbuseEvents(PR_TRUE, PR_FALSE, aUrl, aName, aOptions); - return NS_ERROR_FAILURE; // unlike the public Open method, return an error - } - - rv = OpenInternal(aUrl, aName, aOptions, PR_FALSE, nsnull, 0, nsnull, - _retval); - if (NS_SUCCEEDED(rv)) { - if (abuseLevel >= openControlled && allowReason != allowSelf) { - nsGlobalWindow *opened = NS_STATIC_CAST(nsGlobalWindow *, *_retval); - if (!opened->IsPopupSpamWindow()) { - opened->SetPopupSpamWindow(PR_TRUE); - ++gOpenPopupSpamCount; - } - } - if (abuseLevel >= openAbused) - FireAbuseEvents(PR_FALSE, PR_TRUE, aUrl, aName, aOptions); - } - return rv; + return OpenInternal(aUrl, aName, aOptions, + PR_FALSE, // aDialog + PR_TRUE, // aCalledNoScript + PR_FALSE, // aDoJSFixups + nsnull, 0, nsnull, // No args + _retval); } NS_IMETHODIMP @@ -4275,51 +4230,12 @@ nsGlobalWindow::Open(nsIDOMWindow **_retval) } } - PopupControlState abuseLevel = CheckForAbusePoint(); - OpenAllowValue allowReason = CheckOpenAllow(abuseLevel, name); - if (allowReason == allowNot) { - FireAbuseEvents(PR_TRUE, PR_FALSE, url, name, options); - return NS_OK; // don't open the window, but also don't throw a JS exception - } - - rv = OpenInternal(url, name, options, PR_FALSE, nsnull, 0, nsnull, _retval); - - nsCOMPtr chrome_win(do_QueryInterface(*_retval)); - - if (NS_SUCCEEDED(rv)) { - 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. - -#ifdef DEBUG_jst - { - nsCOMPtr pidomwin(do_QueryInterface(*_retval)); - - nsIDOMDocument *temp = pidomwin->GetExtantDocument(); - - NS_ASSERTION(temp, "No document in new window!!!"); - } -#endif - - nsCOMPtr doc; - (*_retval)->GetDocument(getter_AddRefs(doc)); - } - - if (abuseLevel >= openControlled && allowReason != allowSelf) { - nsGlobalWindow *opened = NS_STATIC_CAST(nsGlobalWindow*, *_retval); - if (!opened->IsPopupSpamWindow()) { - opened->SetPopupSpamWindow(PR_TRUE); - ++gOpenPopupSpamCount; - } - } - if (abuseLevel >= openAbused) - FireAbuseEvents(PR_FALSE, PR_TRUE, url, name, options); - } - - return rv; + return OpenInternal(url, name, options, + PR_FALSE, // aDialog + PR_FALSE, // aCalledNoScript + PR_TRUE, // aDoJSFixups + nsnull, 0, nsnull, // No args + _retval); } // like Open, but attaches to the new window any extra parameters past @@ -4329,8 +4245,12 @@ nsGlobalWindow::OpenDialog(const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions, nsISupports* aExtraArgument, nsIDOMWindow** _retval) { - return OpenInternal(aUrl, aName, aOptions, PR_TRUE, nsnull, 0, - aExtraArgument, _retval); + return OpenInternal(aUrl, aName, aOptions, + PR_TRUE, // aDialog + PR_TRUE, // aCalledNoScript + PR_FALSE, // aDoJSFixups + nsnull, 0, aExtraArgument, // Arguments + _retval); } NS_IMETHODIMP @@ -4373,7 +4293,11 @@ nsGlobalWindow::OpenDialog(nsIDOMWindow** _retval) } } - return OpenInternal(url, name, options, PR_TRUE, argv, argc, nsnull, + return OpenInternal(url, name, options, + PR_TRUE, // aDialog + PR_FALSE, // aCalledNoScript + PR_FALSE, // aDoJSFixups + argv, argc, nsnull, // Arguments _retval); } @@ -5657,17 +5581,28 @@ nsGlobalWindow::GetParentInternal() return parentInternal; } -NS_IMETHODIMP +nsresult nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions, PRBool aDialog, + PRBool aCalledNoScript, PRBool aDoJSFixups, jsval *argv, PRUint32 argc, nsISupports *aExtraArgument, nsIDOMWindow **aReturn) { - FORWARD_TO_OUTER(OpenInternal, (aUrl, aName, aOptions, aDialog, argv, argc, - aExtraArgument, aReturn), + FORWARD_TO_OUTER(OpenInternal, (aUrl, aName, aOptions, aDialog, + aCalledNoScript, aDoJSFixups, + argv, argc, aExtraArgument, aReturn), NS_ERROR_NOT_INITIALIZED); + NS_PRECONDITION(!aExtraArgument || (!argv && argc == 0), + "Can't pass in arguments both ways"); + NS_PRECONDITION(!aCalledNoScript || (!argv && argc == 0), + "Can't pass JS args when called via the noscript methods"); + NS_PRECONDITION(!aDoJSFixups || !aCalledNoScript, + "JS fixups should not be done when called noscript"); + + *aReturn = nsnull; + nsCOMPtr chrome; GetWebBrowserChrome(getter_AddRefs(chrome)); if (!chrome) { @@ -5675,11 +5610,29 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, // -- see nsIWindowWatcher.idl return NS_ERROR_NOT_AVAILABLE; } - - nsXPIDLCString url; - nsresult rv = NS_OK; - *aReturn = nsnull; + NS_ASSERTION(mDocShell, "Must have docshell here"); + + const PRBool checkForPopup = + !aDialog && !WindowExists(aName, !aCalledNoScript); + + // These next two variables are only accessed when checkForPopup is true + PopupControlState abuseLevel; + OpenAllowValue allowReason; + if (checkForPopup) { + abuseLevel = CheckForAbusePoint(); + allowReason = CheckOpenAllow(abuseLevel); + if (allowReason == allowNot) { + FireAbuseEvents(PR_TRUE, PR_FALSE, aUrl, aName, aOptions); + return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE; + } + } + + // Note: it's very important that this be an nsXPIDLCString, since we want + // .get() on it to return nsnull until we write stuff to it. The window + // watcher expects a null URL string if there is no URL to load. + nsXPIDLCString url; + nsresult rv = NS_OK; if (!aUrl.IsEmpty()) { // fix bug 35076 @@ -5708,150 +5661,71 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, if (NS_FAILED(rv)) return rv; - // determine whether we must divert the open window to a new tab. - - PRBool divertOpen = !WindowExists(aName); - - // also check what the prefs prescribe? - // XXXbz this duplicates docshell code. Need to consolidate. - - PRInt32 containerPref = nsIBrowserDOMWindow::OPEN_NEWWINDOW; - - nsCOMPtr tabURI; - if (!aUrl.IsEmpty()) { - PRBool whoCares; - BuildURIfromBase(url.get(), getter_AddRefs(tabURI), &whoCares, 0); - } - - if (divertOpen) { // no such named window - divertOpen = PR_FALSE; // more tests to pass: - if (!aExtraArgument) { - nsCOMPtr thisChrome = - do_QueryInterface(NS_STATIC_CAST(nsIDOMWindow *, this)); - PRBool chromeTab = PR_FALSE; - if (tabURI) - tabURI->SchemeIs("chrome", &chromeTab); - - if (!thisChrome && !chromeTab) { - containerPref = - nsContentUtils::GetIntPref("browser.link.open_newwindow", - nsIBrowserDOMWindow::OPEN_NEWWINDOW); - PRInt32 restrictionPref = nsContentUtils::GetIntPref( - "browser.link.open_newwindow.restriction"); - /* The restriction pref is a power-user's fine-tuning pref. values: - 0: no restrictions - divert everything - 1: don't divert window.open at all - 2: don't divert window.open with features */ - - if (containerPref == nsIBrowserDOMWindow::OPEN_NEWTAB || - containerPref == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { - divertOpen = restrictionPref != 1; - if (divertOpen && !aOptions.IsEmpty() && restrictionPref == 2) - divertOpen = PR_FALSE; - } - } - } - } - nsCOMPtr domReturn; - // divert the window.open into a new tab or into this window, if required + nsCOMPtr wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_TRUE(wwatch, rv); - if (divertOpen) { - if (containerPref == nsIBrowserDOMWindow::OPEN_NEWTAB || - !aUrl.IsEmpty()) { -#ifdef DEBUG - printf("divert window.open to new tab\n"); -#endif - // get nsIBrowserDOMWindow interface + NS_ConvertUTF16toUTF8 options(aOptions); + NS_ConvertUTF16toUTF8 name(aName); - nsCOMPtr bwin; + const char *options_ptr = aOptions.IsEmpty() ? nsnull : options.get(); + const char *name_ptr = aName.IsEmpty() ? nsnull : name.get(); - nsCOMPtr docItem(do_QueryInterface(mDocShell)); - if (docItem) { - nsCOMPtr rootItem; - docItem->GetRootTreeItem(getter_AddRefs(rootItem)); - nsCOMPtr rootWin(do_GetInterface(rootItem)); - nsCOMPtr chromeWin(do_QueryInterface(rootWin)); - if (chromeWin) - chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); - } + { + // Reset popup state while opening a window to prevent the + // current state from being active the whole time a modal + // dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused, PR_TRUE); - // open new tab - - if (bwin) { - // open the tab with the URL - // discard features (meaningless in this case) - bwin->OpenURI(tabURI, this, - containerPref, nsIBrowserDOMWindow::OPEN_NEW, - getter_AddRefs(domReturn)); - - nsCOMPtr domWin(do_GetInterface(domReturn)); - if (domWin) { - domWin->SetOpenerWindow(this); - } - } + if (!aCalledNoScript) { + nsCOMPtr pwwatch(do_QueryInterface(wwatch)); + NS_ASSERTION(pwwatch, + "Unable to open windows from JS because window watcher " + "is broken"); + NS_ENSURE_TRUE(pwwatch, NS_ERROR_UNEXPECTED); + + PRUint32 extraArgc = argc >= 3 ? argc - 3 : 0; + rv = pwwatch->OpenWindowJS(this, url.get(), name_ptr, options_ptr, + aDialog, extraArgc, argv + 3, + getter_AddRefs(domReturn)); } else { -#ifdef DEBUG - printf("divert window.open to current window\n"); -#endif - GetTop(getter_AddRefs(domReturn)); - } + // Push a null JSContext 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 + // nsIDOMWindowInternal::OpenWindow and nsIWindowWatcher::OpenWindow. + nsCOMPtr stack = + do_GetService(sJSStackContractID); - if (domReturn && !aName.LowerCaseEqualsLiteral("_blank") && - !aName.LowerCaseEqualsLiteral("_new")) - domReturn->SetName(aName); - } + if (stack) { + rv = stack->Push(nsnull); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = wwatch->OpenWindow(this, url.get(), name_ptr, options_ptr, + aExtraArgument, getter_AddRefs(domReturn)); - // lacking specific instructions, or just as an error fallback, - // open a new window. - - if (!domReturn) { - nsCOMPtr wwatch = - do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); - - if (wwatch) { - NS_ConvertUTF16toUTF8 options(aOptions); - NS_ConvertUTF16toUTF8 name(aName); - - const char *options_ptr = aOptions.IsEmpty() ? nsnull : options.get(); - const char *name_ptr = aName.IsEmpty() ? nsnull : name.get(); - - { - // Reset popup state while opening a window to prevent the - // current state from being active the whole time a modal - // dialog is open. - nsAutoPopupStatePusher popupStatePusher(openAbused, PR_TRUE); - - if (argc) { - nsCOMPtr pwwatch(do_QueryInterface(wwatch)); - if (pwwatch) { - PRUint32 extraArgc = argc >= 3 ? argc - 3 : 0; - rv = pwwatch->OpenWindowJS(this, url.get(), name_ptr, options_ptr, - aDialog, extraArgc, argv + 3, - getter_AddRefs(domReturn)); - } else { - NS_ERROR("WindowWatcher service not a nsPIWindowWatcher!"); - - rv = NS_ERROR_UNEXPECTED; - } - } else { - rv = wwatch->OpenWindow(this, url.get(), name_ptr, options_ptr, - aExtraArgument, getter_AddRefs(domReturn)); - } + if (stack) { + JSContext* cx; + stack->Pop(&cx); + NS_ASSERTION(!cx, "Unexpected JSContext popped!"); } } } + NS_ENSURE_SUCCESS(rv, rv); + // success! if (domReturn) { - CallQueryInterface(domReturn, aReturn); - // Save the principal of the calling script // We need it to decide whether to clear the scope in SetNewDocument NS_ASSERTION(sSecMan, "No Security Manager Found!"); - if (sSecMan) { + // Note that the opener script URL is not relevant for openDialog + // callers, since those already have chrome privileges. So we + // only want to do this wen aDoJSFixups is true. + if (aDoJSFixups && sSecMan) { nsCOMPtr principal; sSecMan->GetSubjectPrincipal(getter_AddRefs(principal)); if (principal) { @@ -5863,8 +5737,50 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, } } } + + domReturn.swap(*aReturn); } + + + if (NS_SUCCEEDED(rv)) { + if (aDoJSFixups) { + nsCOMPtr chrome_win(do_QueryInterface(*aReturn)); + 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()? +#ifdef DEBUG_jst + { + nsCOMPtr pidomwin(do_QueryInterface(*aReturn)); + + nsIDOMDocument *temp = pidomwin->GetExtantDocument(); + + NS_ASSERTION(temp, "No document in new window!!!"); + } +#endif + + nsCOMPtr doc; + (*aReturn)->GetDocument(getter_AddRefs(doc)); + } + } + + if (checkForPopup) { + if (abuseLevel >= openControlled) { + nsGlobalWindow *opened = NS_STATIC_CAST(nsGlobalWindow *, *aReturn); + if (!opened->IsPopupSpamWindow()) { + opened->SetPopupSpamWindow(PR_TRUE); + ++gOpenPopupSpamCount; + } + } + if (abuseLevel >= openAbused) + FireAbuseEvents(PR_FALSE, PR_TRUE, aUrl, aName, aOptions); + } + } + return rv; } diff --git a/dom/src/base/nsGlobalWindow.h b/dom/src/base/nsGlobalWindow.h index 2e8c34d9ed3..de1ceb444f0 100644 --- a/dom/src/base/nsGlobalWindow.h +++ b/dom/src/base/nsGlobalWindow.h @@ -321,11 +321,46 @@ protected: } // Window Control Functions - NS_IMETHOD OpenInternal(const nsAString& aUrl, - const nsAString& aName, - const nsAString& aOptions, - PRBool aDialog, jsval *argv, PRUint32 argc, - nsISupports *aExtraArgument, nsIDOMWindow **aReturn); + /** + * @param aURL the URL to load in the new window + * @param aName the name to use for the new window + * @param aOptions the window options to use for the new window + * @param aDialog true when called from variants of OpenDialog. If this is + * true, this method will skip popup blocking checks. The + * aDialog argument is passed on to the window watcher. + * @param aCalledNoScript true when called via the [noscript] open() + * and openDialog() methods. When this is true, we do + * NOT want to use the JS stack for things like caller + * determination. + * @param aDoJSFixups true when this is the content-accessible JS version of + * window opening. When true, popups do not cause us to + * throw, we save the caller's principal in the new window + * for later consumption, and we make sure that there is a + * document in the newly-opened window. Note that this + * last will only be done if the newly-opened window is + * non-chrome. + * @param argv The arguments to pass to the new window. The first + * three args, if present, will be aURL, aName, and aOptions. So + * this param only matters if there are more than 3 arguments. + * @param argc The number of arguments in argv. + * @param aExtraArgument Another way to pass arguments in. This is mutually + * exclusive with the argv/argc approach. + * @param aReturn [out] The window that was opened, if any. + * + * @note that the boolean args are const because the function shouldn't be + * messing with them. That also makes it easier for the compiler to sort out + * its build warning stuff. + */ + NS_HIDDEN_(nsresult) OpenInternal(const nsAString& aUrl, + const nsAString& aName, + const nsAString& aOptions, + PRBool aDialog, + PRBool aCalledNoScript, + PRBool aDoJSFixups, + jsval *argv, PRUint32 argc, + nsISupports *aExtraArgument, + nsIDOMWindow **aReturn); + static void CloseWindow(nsISupports* aWindow); static void ClearWindowScope(nsISupports* aWindow); @@ -350,8 +385,7 @@ protected: nsIURI **aBuiltURI, PRBool *aFreeSecurityPass, JSContext **aCXused); PopupControlState CheckForAbusePoint(); - OpenAllowValue CheckOpenAllow(PopupControlState aAbuseLevel, - const nsAString &aName); + OpenAllowValue CheckOpenAllow(PopupControlState aAbuseLevel); void FireAbuseEvents(PRBool aBlocked, PRBool aWindow, const nsAString &aPopupURL, const nsAString &aPopupWindowName, @@ -390,7 +424,10 @@ protected: PRBool DispatchCustomEvent(const char *aEventName); - PRBool WindowExists(const nsAString& aName); + // 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. + PRBool WindowExists(const nsAString& aName, PRBool aLookForCallerOnJSStack); already_AddRefed GetMainWidget(); diff --git a/embedding/base/Makefile.in b/embedding/base/Makefile.in index 40116168bf0..131cdf08dc6 100644 --- a/embedding/base/Makefile.in +++ b/embedding/base/Makefile.in @@ -78,6 +78,7 @@ SDK_LIBRARY = \ XPIDLSRCS = \ nsIWindowCreator2.idl \ + nsIWindowProvider.idl \ $(NULL) include $(srcdir)/objs.mk diff --git a/embedding/base/nsIWindowProvider.idl b/embedding/base/nsIWindowProvider.idl new file mode 100644 index 00000000000..ba285e2ce88 --- /dev/null +++ b/embedding/base/nsIWindowProvider.idl @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla.org code. + * + * The Initial Developer of the Original Code is Mozilla.com. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Boris Zbarsky (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * nsIWindowProvider is a callback interface used by Gecko when it needs to + * open a new window. This interface can be implemented by Gecko consumers who + * wish to provide a custom "new window" of their own (for example by returning + * a new tab, an existing window, etc) instead of just having a real new + * toplevel window open. + * + * @status UNDER_REVIEW ??? + */ + +#include "nsISupports.idl" + +interface nsIDOMWindow; +interface nsIURI; + +/** + * The nsIWindowProvider interface exists so that the window watcher's default + * behavior of opening a new window can be easly modified. When the window + * watcher needs to open a new window, it will first check with the + * nsIWindowProvider it gets from the parent window. If there is no provider + * or the provider does not provide a window, the window watcher will proceed + * to actually open a new window. + */ +[uuid(A4D32E30-9854-477A-83CB-FB7B8AE9273C)] +interface nsIWindowProvider : nsISupports +{ + /** + * A method to request that this provider provide a window. The window + * returned need not to have the right name or parent set on it; setting + * those is the caller's responsibility. The provider can always return null + * to have the caller create a brand-new window. + * + * @param aParent Must not be null. This is the window that the caller wants + * to use as the parent for the new window. Generally, + * nsIWindowProvider implementors can expect to be somehow + * related to aParent; the relationship may depend on the + * nsIWindowProvider implementation. + * @param aChromeFlags The chrome flags the caller will use to create a new + * window if this provider returns null. See + * nsIWebBrowserChrome for the possible values of this + * field. + * @param aPositionSpecified Whether the attempt to create a window is trying + * to specify a position for the new window. + * @param aSizeSpecified Whether the attempt to create a window is trying to + * specify a size for the new window. + * @param aURI The URI to be loaded in the new window. The nsIWindowProvider + * implementation MUST NOT load this URI in the window it + * returns. This URI is provided solely to help the + * nsIWindowProvider implenentation make decisions; the caller + * will handle loading the URI in the window returned if + * provideWindow returns a window. Note that the URI may be null + * if the load cannot be represented by a single URI (e.g. if + * the load has extra load flags, POST data, etc). + * @param aName The name of the window being opened. Setting the name on the + * return value of provideWindow will be handled by the caller; + * aName is provided solely to help the nsIWindowProvider + * implementation make decisions. + * @param aFeatures The feature string for the window being opened. This may + * be empty. The nsIWindowProvider implementation is + * allowed to apply the feature string to the window it + * returns in any way it sees fit. See the nsIWindowWatcher + * interface for details on feature strings. + * @return A window the caller should use or null if the caller should just + * create a new window. The returned window may be newly opened by + * the nsIWindowProvider implementation or may be a window that + * already existed. + * + * @see nsIWindowWatcher for more information on aFeatures. + * @see nsIWebBrowserChrome for more information on aChromeFlags. + */ + nsIDOMWindow provideWindow(in nsIDOMWindow aParent, + in unsigned long aChromeFlags, + in boolean aPositionSpecified, + in boolean aSizeSpecified, + in nsIURI aURI, + in AString aName, + in AUTF8String aFeatures); +}; diff --git a/embedding/components/windowwatcher/public/nsIWindowWatcher.idl b/embedding/components/windowwatcher/public/nsIWindowWatcher.idl index 6ecca124461..cebe85214ab 100644 --- a/embedding/components/windowwatcher/public/nsIWindowWatcher.idl +++ b/embedding/components/windowwatcher/public/nsIWindowWatcher.idl @@ -80,13 +80,22 @@ interface nsIWindowWatcher : nsISupports { method will effectively act as if aParent were null. @param aURL url to which to open the new window. Must already be escaped, if applicable. can be null. - @param aName window name from JS window.open. can be null. + @param aName window name from JS window.open. can be null. If a window + with this name already exists, the openWindow call may just load + aUrl in it (if aUrl is not null) and return it. @param aFeatures window features from JS window.open. can be null. @param aArguments extra argument(s) to the new window, to be attached as the |arguments| property. An nsISupportsArray will be unwound into multiple arguments (but not recursively!). can be null. @return the new window + + @note This method may examine the JS context stack for purposes of + determining the security context to use for the search for a given + window named aName. + @note This method should try to set the default charset for the new + window to the default charset of aParent. This is not guaranteed, + however. */ nsIDOMWindow openWindow(in nsIDOMWindow aParent, in string aUrl, in string aName, in string aFeatures, diff --git a/embedding/components/windowwatcher/public/nsPIWindowWatcher.idl b/embedding/components/windowwatcher/public/nsPIWindowWatcher.idl index 6275846315b..5697db08da9 100644 --- a/embedding/components/windowwatcher/public/nsPIWindowWatcher.idl +++ b/embedding/components/windowwatcher/public/nsPIWindowWatcher.idl @@ -1,4 +1,4 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 @@ -79,13 +79,23 @@ interface nsPIWindowWatcher : nsISupports method will effectively act as if aParent were null. @param aURL url to which to open the new window. Must already be escaped, if applicable. can be null. - @param aName window name from JS window.open. can be null. + @param aName window name from JS window.open. can be null. If a window + with this name already exists, the openWindow call may just load + aUrl in it (if aUrl is not null) and return it. @param aFeatures window features from JS window.open. can be null. @param aDialog use dialog defaults (see nsIDOMWindowInternal::openDialog) @param argc count of argv arguments @param argv extra JS arguments, if any (see nsIDOMWindowInternal::openDialog) @return the new window + + @note This method may examine the JS context stack for purposes of + determining the security context to use for the search for a given + window named aName. + @note This method should try to set the default charset for the new + window to the default charset of the document in the calling window + (which is determined based on the JS stack and the value of + aParent). This is not guaranteed, however. */ nsIDOMWindow openWindowJS(in nsIDOMWindow aParent, in string aUrl, in string aName, in string aFeatures, in boolean aDialog, @@ -115,9 +125,3 @@ interface nsPIWindowWatcher : nsISupports in nsIDocShellTreeItem aOriginalRequestor); }; -%{C++ -// {d535806e-afaf-47d1-8d89-783ad088c62a} -#define NS_PWINDOWWATCHER_IID \ - {0xd535806e, 0xafaf, 0x47d1, {0x8d, 0x89, 0x78, 0x3a, 0xd0, 0x88, 0xc6, 0x2a}} -%} - diff --git a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp index c15d41490e9..23ab2e97bfb 100644 --- a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp +++ b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp @@ -81,6 +81,7 @@ #include "nsIMarkupDocumentViewer.h" #include "nsIContentViewer.h" #include "nsIDocumentViewer.h" +#include "nsIWindowProvider.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" @@ -470,8 +471,8 @@ nsWindowWatcher::OpenWindow(nsIDOMWindow *aParent, rv = ConvertSupportsTojsvals(aParent, aArguments, &argc, &argv, &cx, &mark); if (NS_SUCCEEDED(rv)) { PRBool dialog = argc == 0 ? PR_FALSE : PR_TRUE; - rv = OpenWindowJS(aParent, aUrl, aName, aFeatures, dialog, argc, argv, - _retval); + rv = OpenWindowJSInternal(aParent, aUrl, aName, aFeatures, dialog, argc, + argv, PR_FALSE, _retval); if (argv) { js_FreeStack(cx, mark); @@ -481,6 +482,47 @@ nsWindowWatcher::OpenWindow(nsIDOMWindow *aParent, return rv; } +struct SizeSpec { + SizeSpec() : + mLeftSpecified(PR_FALSE), + mTopSpecified(PR_FALSE), + mOuterWidthSpecified(PR_FALSE), + mOuterHeightSpecified(PR_FALSE), + mInnerWidthSpecified(PR_FALSE), + mInnerHeightSpecified(PR_FALSE), + mUseDefaultWidth(PR_FALSE), + mUseDefaultHeight(PR_FALSE) + {} + + PRInt32 mLeft; + PRInt32 mTop; + PRInt32 mOuterWidth; // Total window width + PRInt32 mOuterHeight; // Total window height + PRInt32 mInnerWidth; // Content area width + PRInt32 mInnerHeight; // Content area height + + PRPackedBool mLeftSpecified; + PRPackedBool mTopSpecified; + PRPackedBool mOuterWidthSpecified; + PRPackedBool mOuterHeightSpecified; + PRPackedBool mInnerWidthSpecified; + PRPackedBool mInnerHeightSpecified; + + // If these booleans are true, don't look at the corresponding width values + // even if they're specified -- they'll be bogus + PRPackedBool mUseDefaultWidth; + PRPackedBool mUseDefaultHeight; + + PRBool PositionSpecified() const { + return mLeftSpecified || mTopSpecified; + } + + PRBool SizeSpecified() const { + return mOuterWidthSpecified || mOuterHeightSpecified || + mInnerWidthSpecified || mInnerHeightSpecified; + } +}; + NS_IMETHODIMP nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, const char *aUrl, @@ -490,11 +532,27 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, PRUint32 argc, jsval *argv, nsIDOMWindow **_retval) +{ + return OpenWindowJSInternal(aParent, aUrl, aName, aFeatures, aDialog, argc, + argv, PR_TRUE, _retval); +} + +nsresult +nsWindowWatcher::OpenWindowJSInternal(nsIDOMWindow *aParent, + const char *aUrl, + const char *aName, + const char *aFeatures, + PRBool aDialog, + PRUint32 argc, + jsval *argv, + PRBool aCalledFromJS, + nsIDOMWindow **_retval) { nsresult rv = NS_OK; PRBool nameSpecified, featuresSpecified, windowIsNew = PR_FALSE, + windowNeedsName = PR_FALSE, windowIsModal = PR_FALSE, uriToLoadIsChrome = PR_FALSE; PRUint32 chromeFlags; @@ -509,8 +567,7 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, NS_ENSURE_ARG_POINTER(_retval); *_retval = 0; - if (aParent) - GetWindowTreeOwner(aParent, getter_AddRefs(parentTreeOwner)); + GetWindowTreeOwner(aParent, getter_AddRefs(parentTreeOwner)); if (aUrl) { rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad)); @@ -522,7 +579,9 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, nameSpecified = PR_FALSE; if (aName) { CopyUTF8toUTF16(aName, name); +#ifdef DEBUG CheckWindowName(name); +#endif nameSpecified = PR_TRUE; } @@ -534,39 +593,9 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, } // try to find an extant window with the given name - if (nameSpecified) { - nsCOMPtr stack = - do_GetService(sJSStackContractID); - - JSContext *cx = nsnull; - - if (stack) { - stack->Peek(&cx); - } - - nsCOMPtr callerItem; - - if (cx) { - nsCOMPtr callerWebNav = - do_GetInterface(nsWWJSUtils::GetDynamicScriptGlobal(cx)); - - callerItem = do_QueryInterface(callerWebNav); - } - - nsCOMPtr parentItem; - GetWindowTreeItem(aParent, getter_AddRefs(parentItem)); - - if (!callerItem) { - callerItem = parentItem; - } - - if (parentItem) { - parentItem->FindItemWithName(name.get(), nsnull, callerItem, - getter_AddRefs(newDocShellItem)); - } else - FindItemWithName(name.get(), nsnull, callerItem, - getter_AddRefs(newDocShellItem)); - } + nsCOMPtr foundWindow; + SafeGetWindowByName(name, aParent, getter_AddRefs(foundWindow)); + GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem)); // no extant window? make a new one. @@ -580,6 +609,9 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, aDialog, uriToLoadIsChrome, !aParent || chromeParent); + SizeSpec sizeSpec; + CalcSizeSpec(features.get(), sizeSpec); + PRBool isCallerChrome = PR_FALSE; nsCOMPtr sm(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID)); @@ -597,6 +629,39 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, callerContextGuard.Push(cx); } + if (!newDocShellItem) { + // We're going to either open up a new window ourselves or ask a + // nsIWindowProvider for one. In either case, we'll want to set the right + // name on it. + windowNeedsName = PR_TRUE; + + // Now check whether it's ok to ask a window provider for a window. Don't + // do it if we're opening a dialog or if our parent is a chrome window or + // if we're opening something that has dependent, modal, dialog, or chrome + // flags set. + nsCOMPtr chromeWin = do_QueryInterface(aParent); + if (!aDialog && !chromeWin && + !(chromeFlags & (nsIWebBrowserChrome::CHROME_DEPENDENT | + nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) { + nsCOMPtr provider = do_GetInterface(parentTreeOwner); + if (provider) { + NS_ASSERTION(aParent, "We've _got_ to have a parent here!"); + + nsCOMPtr newWindow; + rv = provider->ProvideWindow(aParent, chromeFlags, + sizeSpec.PositionSpecified(), + sizeSpec.SizeSpecified(), + uriToLoad, name, features, + getter_AddRefs(newWindow)); + if (NS_SUCCEEDED(rv)) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + } + } + } + } + if (!newDocShellItem) { windowIsNew = PR_TRUE; @@ -682,6 +747,9 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, if (!newDocShellItem) return rv; + nsCOMPtr newDocShell(do_QueryInterface(newDocShellItem)); + NS_ENSURE_TRUE(newDocShell, NS_ERROR_UNEXPECTED); + rv = ReadyOpenedDocShellItem(newDocShellItem, aParent, _retval); if (NS_FAILED(rv)) return rv; @@ -713,36 +781,58 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, } } - /* allow an extant window to keep its name (important for cases like - _self where the given name is different (and invalid)). also _blank - is not a window name. */ - if (windowIsNew) - newDocShellItem->SetName(nameSpecified && !name.LowerCaseEqualsLiteral("_blank") ? + /* allow a window that we found by name to keep its name (important for cases + like _self where the given name is different (and invalid)). Also, _blank + and _new are not window names. */ + if (windowNeedsName) + newDocShellItem->SetName(nameSpecified && + !name.LowerCaseEqualsLiteral("_blank") && + !name.LowerCaseEqualsLiteral("_new") ? name.get() : nsnull); - nsCOMPtr newDocShell(do_QueryInterface(newDocShellItem)); - // When a new window is opened through JavaScript, we want it to use the - // charset of its opener as a fallback in the event the document being loaded - // does not specify a charset. Failing to set this charset is not fatal, so we - // want to continue in the face of errors. - nsCOMPtr parentWin(do_QueryInterface(aParent)); - if (parentWin) { - nsIDocShell *parentDocshell = parentWin->GetDocShell(); - // parentDocshell may be null if the parent got closed in the meantime - if (parentDocshell) { - nsCOMPtr parentContentViewer; - parentDocshell->GetContentViewer(getter_AddRefs(parentContentViewer)); - nsCOMPtr parentDocViewer(do_QueryInterface(parentContentViewer)); - if (parentDocViewer) { - nsCOMPtr doc; - parentDocViewer->GetDocument(getter_AddRefs(doc)); - - nsCOMPtr newContentViewer; - newDocShell->GetContentViewer(getter_AddRefs(newContentViewer)); - nsCOMPtr newMarkupDocViewer(do_QueryInterface(newContentViewer)); - if (doc && newMarkupDocViewer) { - newMarkupDocViewer->SetDefaultCharacterSet(doc->GetDocumentCharacterSet()); + // Inherit the right character set into the new window to use as a fallback + // in the event the document being loaded does not specify a charset. When + // aCalledFromJS is true, we want to use the character set of the document in + // the caller; otherwise we want to use the character set of aParent's + // docshell. Failing to set this charset is not fatal, so we want to continue + // in the face of errors. + nsCOMPtr newCV; + newDocShell->GetContentViewer(getter_AddRefs(newCV)); + nsCOMPtr newMuCV = do_QueryInterface(newCV); + if (newMuCV) { + nsCOMPtr parentItem; + GetWindowTreeItem(aParent, getter_AddRefs(parentItem)); + + if (aCalledFromJS) { + nsCOMPtr callerItem = GetCallerTreeItem(parentItem); + nsCOMPtr callerWin = do_GetInterface(callerItem); + if (callerWin) { + nsCOMPtr doc = + do_QueryInterface(callerWin->GetExtantDocument()); + if (doc) { + newMuCV->SetDefaultCharacterSet(doc->GetDocumentCharacterSet()); + } + } + } + else { + nsCOMPtr parentDocshell = do_QueryInterface(parentItem); + // parentDocshell may be null if the parent got closed in the meantime + if (parentDocshell) { + nsCOMPtr parentCV; + parentDocshell->GetContentViewer(getter_AddRefs(parentCV)); + nsCOMPtr parentMuCV = + do_QueryInterface(parentCV); + if (parentMuCV) { + nsCAutoString charset; + nsresult res = parentMuCV->GetDefaultCharacterSet(charset); + if (NS_SUCCEEDED(res)) { + newMuCV->SetDefaultCharacterSet(charset); + } + res = parentMuCV->GetPrevDocCharacterSet(charset); + if (NS_SUCCEEDED(res)) { + newMuCV->SetPrevDocCharacterSet(charset); + } } } } @@ -787,8 +877,7 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, // get its document, if any if (stack && NS_SUCCEEDED(stack->Peek(&ccx)) && ccx) { - nsIScriptGlobalObject *sgo = - nsWWJSUtils::GetStaticScriptGlobal(ccx, ::JS_GetGlobalObject(ccx)); + nsIScriptGlobalObject *sgo = nsWWJSUtils::GetDynamicScriptGlobal(ccx); nsCOMPtr w(do_QueryInterface(sgo)); if (w) { @@ -811,8 +900,7 @@ nsWindowWatcher::OpenWindowJS(nsIDOMWindow *aParent, } if (windowIsNew) - SizeOpenedDocShellItem(newDocShellItem, aParent, features.get(), - chromeFlags); + SizeOpenedDocShellItem(newDocShellItem, aParent, sizeSpec); if (windowIsModal) { nsCOMPtr newTreeOwner; @@ -1111,37 +1199,22 @@ nsWindowWatcher::GetWindowByName(const PRUnichar *aTargetName, *aResult = nsnull; - nsCOMPtr webNav; nsCOMPtr treeItem; - // First, check if the TargetName exists in the aCurrentWindow hierarchy - webNav = do_GetInterface(aCurrentWindow); - if (webNav) { - nsCOMPtr docShellTreeItem; - - docShellTreeItem = do_QueryInterface(webNav); - if (docShellTreeItem) { - // Note: original requestor is null here, per idl comments - docShellTreeItem->FindItemWithName(aTargetName, nsnull, nsnull, - getter_AddRefs(treeItem)); - } + nsCOMPtr startItem; + GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem)); + if (startItem) { + // Note: original requestor is null here, per idl comments + startItem->FindItemWithName(aTargetName, nsnull, nsnull, + getter_AddRefs(treeItem)); } - - // Next, see if the TargetName exists in any window hierarchy - if (!treeItem) { + else { // Note: original requestor is null here, per idl comments FindItemWithName(aTargetName, nsnull, nsnull, getter_AddRefs(treeItem)); } - if (treeItem) { - nsCOMPtr domWindow; - - domWindow = do_GetInterface(treeItem); - if (domWindow) { - *aResult = domWindow; - NS_ADDREF(*aResult); - } - } + nsCOMPtr domWindow = do_GetInterface(treeItem); + domWindow.swap(*aResult); return NS_OK; } @@ -1203,6 +1276,7 @@ nsWindowWatcher::URIfromURL(const char *aURL, return NS_NewURI(aURI, aURL, baseURI); } +#ifdef DEBUG /* Check for an illegal name e.g. frame3.1 This just prints a warning message an continues; we open the window anyway, (see bug 32898). */ @@ -1218,15 +1292,14 @@ void nsWindowWatcher::CheckWindowName(nsString& aName) // Don't use js_ReportError as this will cause the application // to shut down (JS_ASSERT calls abort()) See bug 32898 - nsAutoString warn; + nsCAutoString warn; warn.AssignLiteral("Illegal character in window name "); - warn.Append(aName); - char *cp = ToNewCString(warn); - NS_WARNING(cp); - nsCRT::free(cp); + AppendUTF16toUTF8(aName, warn); + NS_WARNING(warn.get()); break; } } +#endif // DEBUG #define NS_CALCULATE_CHROME_FLAG_FOR(feature, flag) \ prefBranch->GetBoolPref(feature, &forceEnable); \ @@ -1513,6 +1586,65 @@ nsWindowWatcher::FindItemWithName(const PRUnichar* aName, return rv; } +already_AddRefed +nsWindowWatcher::GetCallerTreeItem(nsIDocShellTreeItem* aParentItem) +{ + nsCOMPtr stack = + do_GetService(sJSStackContractID); + + JSContext *cx = nsnull; + + if (stack) { + stack->Peek(&cx); + } + + nsIDocShellTreeItem* callerItem = nsnull; + + if (cx) { + nsCOMPtr callerWebNav = + do_GetInterface(nsWWJSUtils::GetDynamicScriptGlobal(cx)); + + if (callerWebNav) { + CallQueryInterface(callerWebNav, &callerItem); + } + } + + if (!callerItem) { + NS_IF_ADDREF(callerItem = aParentItem); + } + + return callerItem; +} + +nsresult +nsWindowWatcher::SafeGetWindowByName(const nsAString& aName, + nsIDOMWindow* aCurrentWindow, + nsIDOMWindow** aResult) +{ + *aResult = nsnull; + + nsCOMPtr startItem; + GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem)); + + nsCOMPtr callerItem = GetCallerTreeItem(startItem); + + const nsAFlatString& flatName = PromiseFlatString(aName); + + nsCOMPtr foundItem; + if (startItem) { + startItem->FindItemWithName(flatName.get(), nsnull, callerItem, + getter_AddRefs(foundItem)); + } + else { + FindItemWithName(flatName.get(), nsnull, callerItem, + getter_AddRefs(foundItem)); + } + + nsCOMPtr foundWin = do_GetInterface(foundItem); + foundWin.swap(*aResult); + return NS_OK; +} + /* Fetch the nsIDOMWindow corresponding to the given nsIDocShellTreeItem. This forces the creation of a script context, if one has not already been created. Note it also sets the window's opener to the parent, @@ -1537,7 +1669,69 @@ nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem *aOpenedItem, return rv; } -/* Size and position the new window according to aFeatures. This method +void +nsWindowWatcher::CalcSizeSpec(const char* aFeatures, SizeSpec& aResult) +{ + // Parse position spec, if any, from aFeatures + PRBool present; + PRInt32 temp; + + present = PR_FALSE; + if ((temp = WinHasOption(aFeatures, "left", 0, &present)) || present) + aResult.mLeft = temp; + else if ((temp = WinHasOption(aFeatures, "screenX", 0, &present)) || present) + aResult.mLeft = temp; + aResult.mLeftSpecified = present; + + present = PR_FALSE; + if ((temp = WinHasOption(aFeatures, "top", 0, &present)) || present) + aResult.mTop = temp; + else if ((temp = WinHasOption(aFeatures, "screenY", 0, &present)) || present) + aResult.mTop = temp; + aResult.mTopSpecified = present; + + // Parse size spec, if any. Chrome size overrides content size. + if ((temp = WinHasOption(aFeatures, "outerWidth", PR_INT32_MIN, nsnull))) { + if (temp == PR_INT32_MIN) { + aResult.mUseDefaultWidth = PR_TRUE; + } + else { + aResult.mOuterWidth = temp; + } + aResult.mOuterWidthSpecified = PR_TRUE; + } else if ((temp = WinHasOption(aFeatures, "width", PR_INT32_MIN, nsnull)) || + (temp = WinHasOption(aFeatures, "innerWidth", PR_INT32_MIN, + nsnull))) { + if (temp == PR_INT32_MIN) { + aResult.mUseDefaultWidth = PR_TRUE; + } else { + aResult.mInnerWidth = temp; + } + aResult.mInnerWidthSpecified = PR_TRUE; + } + + if ((temp = WinHasOption(aFeatures, "outerHeight", PR_INT32_MIN, nsnull))) { + if (temp == PR_INT32_MIN) { + aResult.mUseDefaultHeight = PR_TRUE; + } + else { + aResult.mOuterHeight = temp; + } + aResult.mOuterHeightSpecified = PR_TRUE; + } else if ((temp = WinHasOption(aFeatures, "height", PR_INT32_MIN, + nsnull)) || + (temp = WinHasOption(aFeatures, "innerHeight", PR_INT32_MIN, + nsnull))) { + if (temp == PR_INT32_MIN) { + aResult.mUseDefaultHeight = PR_TRUE; + } else { + aResult.mInnerHeight = temp; + } + aResult.mInnerHeightSpecified = PR_TRUE; + } +} + +/* Size and position the new window according to aSizeSpec. This method is assumed to be called after the window has already been given a default position and size; thus its current position and size are accurate defaults. The new window is made visible at method end. @@ -1545,8 +1739,7 @@ nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem *aOpenedItem, void nsWindowWatcher::SizeOpenedDocShellItem(nsIDocShellTreeItem *aDocShellItem, nsIDOMWindow *aParent, - const char *aFeatures, - PRUint32 aChromeFlags) + const SizeSpec & aSizeSpec) { // position and size of window PRInt32 left = 0, @@ -1585,62 +1778,47 @@ nsWindowWatcher::SizeOpenedDocShellItem(nsIDocShellTreeItem *aDocShellItem, } } - // Parse position spec, if any, from aFeatures - - PRBool positionSpecified = PR_FALSE; - PRBool present; - PRInt32 temp; - - present = PR_FALSE; - if ((temp = WinHasOption(aFeatures, "left", 0, &present)) || present) - left = temp; - else if ((temp = WinHasOption(aFeatures, "screenX", 0, &present)) || present) - left = temp; - if (present) - positionSpecified = PR_TRUE; - - present = PR_FALSE; - if ((temp = WinHasOption(aFeatures, "top", 0, &present)) || present) - top = temp; - else if ((temp = WinHasOption(aFeatures, "screenY", 0, &present)) || present) - top = temp; - if (present) - positionSpecified = PR_TRUE; - - PRBool sizeSpecified = PR_FALSE; - - // Parse size spec, if any. Chrome size overrides content size. - - if ((temp = WinHasOption(aFeatures, "outerWidth", width, nsnull))) { - width = temp; - sizeSpecified = PR_TRUE; - } else if ((temp = WinHasOption(aFeatures, "width", - width - chromeWidth, nsnull))) { - width = temp; - sizeChromeWidth = PR_FALSE; - sizeSpecified = PR_TRUE; - } else if ((temp = WinHasOption(aFeatures, "innerWidth", - width - chromeWidth, nsnull))) { - width = temp; - sizeChromeWidth = PR_FALSE; - sizeSpecified = PR_TRUE; + // Set up left/top + if (aSizeSpec.mLeftSpecified) { + left = aSizeSpec.mLeft; } - if ((temp = WinHasOption(aFeatures, "outerHeight", height, nsnull))) { - height = temp; - sizeSpecified = PR_TRUE; - } else if ((temp = WinHasOption(aFeatures, "height", - height - chromeHeight, nsnull))) { - height = temp; - sizeChromeHeight = PR_FALSE; - sizeSpecified = PR_TRUE; - } else if ((temp = WinHasOption(aFeatures, "innerHeight", - height - chromeHeight, nsnull))) { - height = temp; - sizeChromeHeight = PR_FALSE; - sizeSpecified = PR_TRUE; + if (aSizeSpec.mTopSpecified) { + top = aSizeSpec.mTop; } + // Set up width + if (aSizeSpec.mOuterWidthSpecified) { + if (!aSizeSpec.mUseDefaultWidth) { + width = aSizeSpec.mOuterWidth; + } // Else specified to default; just use our existing width + } + else if (aSizeSpec.mInnerWidthSpecified) { + sizeChromeWidth = PR_FALSE; + if (aSizeSpec.mUseDefaultWidth) { + width = width - chromeWidth; + } else { + width = aSizeSpec.mInnerWidth; + } + } + + // Set up height + if (aSizeSpec.mOuterHeightSpecified) { + if (!aSizeSpec.mUseDefaultHeight) { + height = aSizeSpec.mOuterHeight; + } // Else specified to default; just use our existing height + } + else if (aSizeSpec.mInnerHeightSpecified) { + sizeChromeHeight = PR_FALSE; + if (aSizeSpec.mUseDefaultHeight) { + height = height - chromeHeight; + } else { + height = aSizeSpec.mInnerHeight; + } + } + + PRBool positionSpecified = aSizeSpec.PositionSpecified(); + nsresult res; PRBool enabled = PR_FALSE; @@ -1686,7 +1864,7 @@ nsWindowWatcher::SizeOpenedDocShellItem(nsIDocShellTreeItem *aDocShellItem, screen->GetAvailRect(&screenLeft, &screenTop, &screenWidth, &screenHeight); - if (sizeSpecified) { + if (aSizeSpec.SizeSpecified()) { /* Unlike position, force size out-of-bounds check only if size actually was specified. Otherwise, intrinsically sized windows are broken. */ @@ -1717,7 +1895,7 @@ nsWindowWatcher::SizeOpenedDocShellItem(nsIDocShellTreeItem *aDocShellItem, if (positionSpecified) treeOwnerAsWin->SetPosition(left, top); - if (sizeSpecified) { + if (aSizeSpec.SizeSpecified()) { /* Prefer to trust the interfaces, which think in terms of pure chrome or content sizes. If we have a mix, use the chrome size adjusted by the chrome/content differences calculated earlier. */ diff --git a/embedding/components/windowwatcher/src/nsWindowWatcher.h b/embedding/components/windowwatcher/src/nsWindowWatcher.h index bef0491c414..e370273a0dc 100644 --- a/embedding/components/windowwatcher/src/nsWindowWatcher.h +++ b/embedding/components/windowwatcher/src/nsWindowWatcher.h @@ -59,6 +59,7 @@ struct JSContext; struct JSObject; struct nsWatcherWindowEntry; struct PRLock; +struct SizeSpec; class nsWindowWatcher : public nsIWindowWatcher, @@ -84,12 +85,38 @@ private: nsWatcherWindowEntry *FindWindowEntry(nsIDOMWindow *aWindow); nsresult RemoveWindow(nsWatcherWindowEntry *inInfo); + // Get the caller tree item. Look on the JS stack, then fall back + // to the parent if there's nothing there. + already_AddRefed + GetCallerTreeItem(nsIDocShellTreeItem* aParentItem); + + // Unlike GetWindowByName this will look for a caller on the JS + // stack, and then fall back on aCurrentWindow if it can't find one. + nsresult SafeGetWindowByName(const nsAString& aName, + nsIDOMWindow* aCurrentWindow, + nsIDOMWindow** aResult); + + // Just like OpenWindowJS, but knows whether it got called via OpenWindowJS + // (which means called from script) or called via OpenWindow. + nsresult OpenWindowJSInternal(nsIDOMWindow *aParent, + const char *aUrl, + const char *aName, + const char *aFeatures, + PRBool aDialog, + PRUint32 argc, + jsval *argv, + PRBool aCalledFromJS, + nsIDOMWindow **_retval); + static JSContext *GetJSContextFromWindow(nsIDOMWindow *aWindow); static JSContext *GetJSContextFromCallStack(); static nsresult URIfromURL(const char *aURL, nsIDOMWindow *aParent, nsIURI **aURI); +#ifdef DEBUG static void CheckWindowName(nsString& aName); +#endif + static PRUint32 CalculateChromeFlags(const char *aFeatures, PRBool aFeaturesSpecified, PRBool aDialog, @@ -97,13 +124,14 @@ private: PRBool aHasChromeParent); static PRInt32 WinHasOption(const char *aOptions, const char *aName, PRInt32 aDefault, PRBool *aPresenceFlag); + /* Compute the right SizeSpec based on aFeatures */ + static void CalcSizeSpec(const char* aFeatures, SizeSpec& aResult); static nsresult ReadyOpenedDocShellItem(nsIDocShellTreeItem *aOpenedItem, nsIDOMWindow *aParent, nsIDOMWindow **aOpenedWindow); static void SizeOpenedDocShellItem(nsIDocShellTreeItem *aDocShellItem, nsIDOMWindow *aParent, - const char *aFeatures, - PRUint32 aChromeFlags); + const SizeSpec & aSizeSpec); static nsresult AttachArguments(nsIDOMWindow *aWindow, PRUint32 argc, jsval *argv); static nsresult ConvertSupportsTojsvals(nsIDOMWindow *aWindow, diff --git a/xpfe/appshell/src/Makefile.in b/xpfe/appshell/src/Makefile.in index 58260fddb33..107ee1d4aec 100644 --- a/xpfe/appshell/src/Makefile.in +++ b/xpfe/appshell/src/Makefile.in @@ -71,6 +71,7 @@ REQUIRES = xpcom \ xpconnect \ intl \ windowwatcher \ + embed_base \ caps \ unicharutil \ uconv \ diff --git a/xpfe/appshell/src/nsContentTreeOwner.cpp b/xpfe/appshell/src/nsContentTreeOwner.cpp index 07822a6a061..a73f7e6f10c 100644 --- a/xpfe/appshell/src/nsContentTreeOwner.cpp +++ b/xpfe/appshell/src/nsContentTreeOwner.cpp @@ -50,6 +50,8 @@ #include "nsIDOMElement.h" #include "nsIDOMNodeList.h" #include "nsIDOMWindowInternal.h" +#include "nsIDOMChromeWindow.h" +#include "nsIBrowserDOMWindow.h" #include "nsIDOMXULElement.h" #include "nsIEmbeddingSiteWindow.h" #include "nsIEmbeddingSiteWindow2.h" @@ -60,6 +62,9 @@ #include "nsIPrincipal.h" #include "nsIURIFixup.h" #include "nsCDefaultURIFixup.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIWebNavigation.h" #include "nsIDOMDocument.h" #include "nsIScriptObjectPrincipal.h" @@ -116,6 +121,9 @@ NS_INTERFACE_MAP_BEGIN(nsContentTreeOwner) NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome) NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIWindowProvider) + // XXXbz why not just implement those interfaces directly on this object? + // Should file a followup and fix. NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIEmbeddingSiteWindow, mSiteWindow2) NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIEmbeddingSiteWindow2, mSiteWindow2) NS_INTERFACE_MAP_END @@ -682,6 +690,108 @@ NS_IMETHODIMP nsContentTreeOwner::SetTitle(const PRUnichar* aTitle) return mXULWindow->SetTitle(title.get()); } +//***************************************************************************** +// nsContentTreeOwner: nsIWindowProvider +//***************************************************************************** +NS_IMETHODIMP +nsContentTreeOwner::ProvideWindow(nsIDOMWindow* aParent, + PRUint32 aChromeFlags, + PRBool aPositionSpecified, + PRBool aSizeSpecified, + nsIURI* aURI, + const nsAString& aName, + const nsACString& aFeatures, + nsIDOMWindow** aReturn) +{ + NS_ENSURE_ARG_POINTER(aParent); + + *aReturn = nsnull; + + if (!mXULWindow) { + // Nothing to do here + return NS_OK; + } + +#ifdef DEBUG + nsCOMPtr parentNav = do_GetInterface(aParent); + nsCOMPtr parentOwner = do_GetInterface(parentNav); + NS_ASSERTION(SameCOMIdentity(parentOwner, + NS_STATIC_CAST(nsIDocShellTreeOwner*, this)), + "Parent from wrong docshell tree?"); +#endif + + // First check what our prefs say + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) { + return NS_OK; + } + + nsCOMPtr branch; + prefs->GetBranch("browser.link.", getter_AddRefs(branch)); + if (!branch) { + return NS_OK; + } + + // Where should we open this? + PRInt32 containerPref; + if (NS_FAILED(branch->GetIntPref("open_newwindow", &containerPref))) { + return NS_OK; + } + + if (containerPref != nsIBrowserDOMWindow::OPEN_NEWTAB && + containerPref != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { + // Just open a window normally + return NS_OK; + } + + /* Now check our restriction pref. The restriction pref is a power-user's + fine-tuning pref. values: + 0: no restrictions - divert everything + 1: don't divert window.open at all + 2: don't divert window.open with features + */ + PRInt32 restrictionPref; + if (NS_FAILED(branch->GetIntPref("open_newwindow.restriction", + &restrictionPref)) || + restrictionPref < 0 || + restrictionPref > 2) { + restrictionPref = 2; // Sane default behavior + } + + if (restrictionPref == 1) { + return NS_OK; + } + + if (restrictionPref == 2 && + // Only continue if there are no size/position features and no special + // chrome flags. + (aChromeFlags != nsIWebBrowserChrome::CHROME_ALL || + aPositionSpecified || aSizeSpecified)) { + return NS_OK; + } + + nsCOMPtr domWin; + mXULWindow->GetWindowDOMWindow(getter_AddRefs(domWin)); + nsCOMPtr chromeWin = do_QueryInterface(domWin); + if (!chromeWin) { + // Really odd... but whatever + NS_WARNING("nsXULWindow's DOMWindow is not a chrome window"); + return NS_OK; + } + + nsCOMPtr browserDOMWin; + chromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin)); + if (!browserDOMWin) { + return NS_OK; + } + + // Get a new rendering area from the browserDOMWin. To make this + // safe for cases when it'll try to return an existing window or + // something, get it with a null URI. + return browserDOMWin->OpenURI(nsnull, aParent, containerPref, + nsIBrowserDOMWindow::OPEN_NEW, aReturn); +} + //***************************************************************************** // nsContentTreeOwner: Accessors //***************************************************************************** diff --git a/xpfe/appshell/src/nsContentTreeOwner.h b/xpfe/appshell/src/nsContentTreeOwner.h index 771bbc0d871..efc4e54b339 100644 --- a/xpfe/appshell/src/nsContentTreeOwner.h +++ b/xpfe/appshell/src/nsContentTreeOwner.h @@ -50,6 +50,7 @@ #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIWebBrowserChrome2.h" +#include "nsIWindowProvider.h" class nsXULWindow; class nsSiteWindow2; @@ -57,7 +58,8 @@ class nsSiteWindow2; class nsContentTreeOwner : public nsIDocShellTreeOwner, public nsIBaseWindow, public nsIInterfaceRequestor, - public nsIWebBrowserChrome2 + public nsIWebBrowserChrome2, + public nsIWindowProvider { friend class nsXULWindow; friend class nsSiteWindow2; @@ -70,6 +72,7 @@ public: NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIWEBBROWSERCHROME NS_DECL_NSIWEBBROWSERCHROME2 + NS_DECL_NSIWINDOWPROVIDER protected: nsContentTreeOwner(PRBool fPrimary);