/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * 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 the Mozilla browser. * * The Initial Developer of the Original Code is Netscape * Communications, Inc. Portions created by Netscape are * Copyright (C) 1999, Mozilla. All Rights Reserved. * * Contributor(s): * Chris Saari * Conrad Carlen */ #include "nsContextMenuInfo.h" #include "nsIDocument.h" #include "nsIFrame.h" #include "nsIImageFrame.h" #include "imgIRequest.h" #include "nsIStyleContext.h" #include "nsICanvasFrame.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLElement.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsIDOMHTMLImageElement.h" #include "nsIDOMHTMLAreaElement.h" #include "nsIDOMHTMLLinkElement.h" #include "nsNetUtil.h" //***************************************************************************** // class nsContextMenuInfo //***************************************************************************** NS_IMPL_ISUPPORTS1(nsContextMenuInfo, nsIContextMenuInfo) nsContextMenuInfo::nsContextMenuInfo() : mCachedBGImageRequestNode(nsnull) { } nsContextMenuInfo::~nsContextMenuInfo() { } /* readonly attribute nsIDOMEvent mouseEvent; */ NS_IMETHODIMP nsContextMenuInfo::GetMouseEvent(nsIDOMEvent **aEvent) { NS_ENSURE_ARG_POINTER(aEvent); NS_IF_ADDREF(*aEvent = mMouseEvent); return NS_OK; } /* readonly attribute nsIDOMNode targetNode; */ NS_IMETHODIMP nsContextMenuInfo::GetTargetNode(nsIDOMNode **aNode) { NS_ENSURE_ARG_POINTER(aNode); NS_IF_ADDREF(*aNode = mDOMNode); return NS_OK; } /* readonly attribute AString associatedLink; */ NS_IMETHODIMP nsContextMenuInfo::GetAssociatedLink(nsAString& aHRef) { NS_ENSURE_STATE(mAssociatedLink); aHRef.Truncate(0); nsCOMPtr content(do_QueryInterface(mAssociatedLink)); nsAutoString localName; if (content) content->GetLocalName(localName); nsCOMPtr linkContent; ToLowerCase(localName); if (localName.Equals(NS_LITERAL_STRING("a")) || localName.Equals(NS_LITERAL_STRING("area")) || localName.Equals(NS_LITERAL_STRING("link"))) { PRBool hasAttr; content->HasAttribute(NS_LITERAL_STRING("href"), &hasAttr); if (hasAttr) { linkContent = content; nsCOMPtr anchor(do_QueryInterface(linkContent)); if (anchor) anchor->GetHref(aHRef); else { nsCOMPtr area(do_QueryInterface(linkContent)); if (area) area->GetHref(aHRef); else { nsCOMPtr link(do_QueryInterface(linkContent)); if (link) link->GetHref(aHRef); } } } } else { nsCOMPtr curr; mAssociatedLink->GetParentNode(getter_AddRefs(curr)); while (curr) { content = do_QueryInterface(curr); if (!content) break; content->GetLocalName(localName); ToLowerCase(localName); if (localName.Equals(NS_LITERAL_STRING("a"))) { PRBool hasAttr; content->HasAttribute(NS_LITERAL_STRING("href"), &hasAttr); if (hasAttr) { linkContent = content; nsCOMPtr anchor(do_QueryInterface(linkContent)); if (anchor) anchor->GetHref(aHRef); } else linkContent = nsnull; // Links can't be nested. break; } nsCOMPtr temp = curr; temp->GetParentNode(getter_AddRefs(curr)); } } return NS_OK; } /* readonly attribute imgIContainer imageContainer; */ NS_IMETHODIMP nsContextMenuInfo::GetImageContainer(imgIContainer **aImageContainer) { NS_ENSURE_ARG_POINTER(aImageContainer); NS_ENSURE_STATE(mDOMNode); nsCOMPtr request; GetImageRequest(mDOMNode, getter_AddRefs(request)); if (request) return request->GetImage(aImageContainer); return NS_ERROR_FAILURE; } /* readonly attribute nsIURI imageSrc; */ NS_IMETHODIMP nsContextMenuInfo::GetImageSrc(nsIURI **aURI) { NS_ENSURE_ARG_POINTER(aURI); NS_ENSURE_STATE(mDOMNode); // First try the easy case of our node being a nsIDOMHTMLImageElement nsCOMPtr imgElement(do_QueryInterface(mDOMNode)); if (imgElement) { nsAutoString imgSrcSpec; nsresult rv = imgElement->GetSrc(imgSrcSpec); if (NS_SUCCEEDED(rv)) return NS_NewURI(aURI, NS_ConvertUCS2toUTF8(imgSrcSpec)); } // If not, dig deeper. nsCOMPtr request; GetImageRequest(mDOMNode, getter_AddRefs(request)); if (request) return request->GetURI(aURI); return NS_ERROR_FAILURE; } /* readonly attribute imgIContainer backgroundImageContainer; */ NS_IMETHODIMP nsContextMenuInfo::GetBackgroundImageContainer(imgIContainer **aImageContainer) { NS_ENSURE_ARG_POINTER(aImageContainer); NS_ENSURE_STATE(mDOMNode); nsCOMPtr request; GetBackgroundImageRequest(mDOMNode, getter_AddRefs(request)); if (request) return request->GetImage(aImageContainer); return NS_ERROR_FAILURE; } /* readonly attribute nsIURI backgroundImageSrc; */ NS_IMETHODIMP nsContextMenuInfo::GetBackgroundImageSrc(nsIURI **aURI) { NS_ENSURE_ARG_POINTER(aURI); NS_ENSURE_STATE(mDOMNode); nsCOMPtr request; GetBackgroundImageRequest(mDOMNode, getter_AddRefs(request)); if (request) return request->GetURI(aURI); return NS_ERROR_FAILURE; } //***************************************************************************** nsresult nsContextMenuInfo::GetImageRequest(nsIDOMNode * aDOMNode, imgIRequest ** aRequest) { NS_ENSURE_ARG(aDOMNode); NS_ENSURE_ARG_POINTER(aRequest); // Get content nsCOMPtr content = do_QueryInterface(aDOMNode); NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); // Get Document nsCOMPtr document; content->GetDocument(*getter_AddRefs(document)); NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); // Get shell nsCOMPtr presShell; document->GetShellAt(0, getter_AddRefs(presShell)); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); // Get PresContext nsCOMPtr presContext; presShell->GetPresContext(getter_AddRefs(presContext)); NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); nsIFrame *frame = nsnull; presShell->GetPrimaryFrameFor(content, &frame); // does not AddRef NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); nsIImageFrame *imgFrame = nsnull; frame->QueryInterface(NS_GET_IID(nsIImageFrame), (void **)&imgFrame); // does not AddRef NS_ENSURE_TRUE(imgFrame, NS_ERROR_FAILURE); return imgFrame->GetImageRequest(aRequest); } PRBool nsContextMenuInfo::HasBackgroundImage(nsIDOMNode * aDOMNode) { NS_ENSURE_ARG(aDOMNode); nsCOMPtr request; GetBackgroundImageRequest(aDOMNode, getter_AddRefs(request)); return (request != nsnull); } nsresult nsContextMenuInfo::GetBackgroundImageRequest(nsIDOMNode * aDOMNode, imgIRequest ** aRequest) { nsresult rv = NS_ERROR_FAILURE; NS_ENSURE_ARG(aDOMNode); NS_ENSURE_ARG_POINTER(aRequest); if (mCachedBGImageRequest && (aDOMNode == mCachedBGImageRequestNode)) { *aRequest = mCachedBGImageRequest; NS_ADDREF(*aRequest); return NS_OK; } // Get content nsCOMPtr content = do_QueryInterface(aDOMNode); NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); // Get Document nsCOMPtr document; content->GetDocument(*getter_AddRefs(document)); NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); // Get shell nsCOMPtr presShell; document->GetShellAt(0, getter_AddRefs(presShell)); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); // Get PresContext nsCOMPtr presContext; presShell->GetPresContext(getter_AddRefs(presContext)); NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); const nsStyleBackground* bg; nsIFrame* frame = nsnull; rv = presShell->GetPrimaryFrameFor(content, &frame); if (NS_SUCCEEDED(rv) && frame) { // look for a background image on the element nsCOMPtr styleContext; rv = presShell->GetStyleContextFor(frame, getter_AddRefs(styleContext)); if (NS_FAILED(rv) || !styleContext) return rv; do { bg = (const nsStyleBackground*) styleContext->GetStyleData(eStyleStruct_Background); styleContext = dont_AddRef(styleContext->GetParent()); } while (bg && bg->mBackgroundImage.IsEmpty() && styleContext.get()); if (bg && !bg->mBackgroundImage.IsEmpty()) { nsIFrame *pBGFrame = nsnull; rv = GetFrameForBackgroundUpdate(presContext, frame, &pBGFrame); if (NS_SUCCEEDED(rv) && pBGFrame) { // Lookup the background image mCachedBGImageRequestNode = aDOMNode; rv = presContext->LoadImage(bg->mBackgroundImage, pBGFrame, getter_AddRefs(mCachedBGImageRequest)); *aRequest = mCachedBGImageRequest; NS_IF_ADDREF(*aRequest); return rv; } } } // if (NS_SUCCEEDED(rv) && frame) // nothing on the element or its parent style contexts, fall back to canvas frame for the whole page rv = NS_ERROR_FAILURE; nsCOMPtr rootContent; document->GetRootContent(getter_AddRefs(rootContent)); NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE); presShell->GetPrimaryFrameFor(rootContent, &frame); NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); frame->GetParent(&frame); NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); nsICanvasFrame* canvasFrame; if (NS_SUCCEEDED(frame->QueryInterface(NS_GET_IID(nsICanvasFrame), (void**)&canvasFrame))) { PRBool isCanvas; FindBackground(presContext, frame, &bg, &isCanvas); if (bg && !bg->mBackgroundImage.IsEmpty()) { nsIFrame *pBGFrame = nsnull; rv = GetFrameForBackgroundUpdate(presContext, frame, &pBGFrame); if (NS_SUCCEEDED(rv) && pBGFrame) { // Lookup the background image mCachedBGImageRequestNode = aDOMNode; rv = presContext->LoadImage(bg->mBackgroundImage, pBGFrame, getter_AddRefs(mCachedBGImageRequest)); *aRequest = mCachedBGImageRequest; NS_IF_ADDREF(*aRequest); return rv; } } } return rv; } //////// // Everything following this is copied from nsCSSRendering.cpp. The methods were not publically available, // Perhaps there is a better, pubically supported way to get the same thing done? //////// // nethod GetFrameForBackgroundUpdate // // If the frame (aFrame) is the HTML or BODY frame then find the canvas frame and set the // aBGFrame param to that. This is used when we need a frame to invalidate after an asynch // image load for the background. // // The check is a bit expensive, however until the canvas frame is somehow cached on the // body frame, or the root element, we need to walk the frames up until we find the canvas // nsresult nsContextMenuInfo::GetFrameForBackgroundUpdate(nsIPresContext *aPresContext,nsIFrame *aFrame, nsIFrame **aBGFrame) { NS_ASSERTION(aFrame && aBGFrame, "illegal null parameter"); nsresult rv = NS_OK; if (aFrame && aBGFrame) { *aBGFrame = aFrame; // default to the frame passed in nsCOMPtr pContent; aFrame->GetContent(getter_AddRefs(pContent)); if (pContent) { // make sure that this is the HTML or BODY element nsCOMPtr tag; pContent->GetTag(*(getter_AddRefs(tag))); nsCOMPtr mTag_html = getter_AddRefs(NS_NewAtom("html")); nsCOMPtr mTag_body = getter_AddRefs(NS_NewAtom("body")); if (tag && tag.get() == mTag_html || tag.get() == mTag_body) { // the frame is the body frame, so we provide the canvas frame nsIFrame *pCanvasFrame = nsnull; aFrame->GetParent(&pCanvasFrame); while (pCanvasFrame) { nsCOMPtr parentType; pCanvasFrame->GetFrameType(getter_AddRefs(parentType)); nsCOMPtr mTag_canvasFrame = getter_AddRefs(NS_NewAtom("CanvasFrame")); if (parentType.get() == mTag_canvasFrame) { *aBGFrame = pCanvasFrame; break; } pCanvasFrame->GetParent(&pCanvasFrame); } }// if tag == html or body } } else { rv = NS_ERROR_NULL_POINTER; } return rv; } inline PRBool nsContextMenuInfo::FindCanvasBackground(nsIPresContext* aPresContext, nsIFrame* aForFrame, const nsStyleBackground** aBackground) { // XXXldb What if the root element is positioned, etc.? (We don't // allow that yet, do we?) nsIFrame *firstChild; aForFrame->FirstChild(aPresContext, nsnull, &firstChild); if (firstChild) { const nsStyleBackground *result; GetStyleData(firstChild, &result); // for printing and print preview.. this should be a pageContentFrame nsCOMPtr frameType; nsCOMPtr parentContext; firstChild->GetFrameType(getter_AddRefs(frameType)); nsCOMPtr mTag_pageContentFrame = getter_AddRefs(NS_NewAtom("PageContentFrame")); if ( (frameType == mTag_pageContentFrame) ){ // we have to find the background style ourselves.. since the // pageContentframe does not have content while(firstChild){ for (nsIFrame* kidFrame = firstChild; nsnull != kidFrame; ) { kidFrame->GetStyleContext(getter_AddRefs(parentContext)); result = (nsStyleBackground*)parentContext->GetStyleData(eStyleStruct_Background); if (!result->BackgroundIsTransparent()){ GetStyleData(kidFrame, aBackground); return PR_TRUE; } else { kidFrame->GetNextSibling(&kidFrame); } } firstChild->FirstChild(aPresContext, nsnull, &firstChild); } return PR_FALSE; // nothing found for this } // Check if we need to do propagation from BODY rather than HTML. if (result->BackgroundIsTransparent()) { nsCOMPtr content; aForFrame->GetContent(getter_AddRefs(content)); if (content) { nsCOMPtr node( do_QueryInterface(content) ); // Use |GetOwnerDocument| so it works during destruction. nsCOMPtr doc; node->GetOwnerDocument(getter_AddRefs(doc)); nsCOMPtr htmlDoc = do_QueryInterface(doc); if (htmlDoc) { nsCOMPtr body; htmlDoc->GetBody(getter_AddRefs(body)); nsCOMPtr bodyContent = do_QueryInterface(body); // We need to null check the body node (bug 118829) since // there are cases, thanks to the fix for bug 5569, where we // will reflow a document with no body. In particular, if a // SCRIPT element in the head blocks the parser and then has a // SCRIPT that does "document.location.href = 'foo'", then // nsParser::Terminate will call |DidBuildModel| methods // through to the content sink, which will call |StartLayout| // and thus |InitialReflow| on the pres shell. See bug 119351 // for the ugly details. if (bodyContent) { nsCOMPtr shell; aPresContext->GetShell(getter_AddRefs(shell)); nsIFrame *bodyFrame; nsresult rv = shell->GetPrimaryFrameFor(bodyContent, &bodyFrame); if (NS_SUCCEEDED(rv) && bodyFrame) ::GetStyleData(bodyFrame, &result); } } } } *aBackground = result; } else { // This should always give transparent, so we'll fill it in with the // default color if needed. This seems to happen a bit while a page is // being loaded. GetStyleData(aForFrame, aBackground); } return PR_TRUE; } inline PRBool nsContextMenuInfo::FindElementBackground(nsIPresContext* aPresContext, nsIFrame* aForFrame, const nsStyleBackground** aBackground) { nsIFrame *parentFrame; aForFrame->GetParent(&parentFrame); // XXXldb We shouldn't have to null-check |parentFrame| here. if (parentFrame && IsCanvasFrame(parentFrame)) { // Check that we're really the root (rather than in another child list). nsIFrame *childFrame; parentFrame->FirstChild(aPresContext, nsnull, &childFrame); if (childFrame == aForFrame) return PR_FALSE; // Background was already drawn for the canvas. } ::GetStyleData(aForFrame, aBackground); nsCOMPtr content; aForFrame->GetContent(getter_AddRefs(content)); if (!content || !content->IsContentOfType(nsIContent::eHTML)) return PR_TRUE; // not frame for an HTML element if (!parentFrame) return PR_TRUE; // no parent to look at nsCOMPtr tag; content->GetTag(*getter_AddRefs(tag)); nsCOMPtr mTag_body = getter_AddRefs(NS_NewAtom("body")); if (tag != mTag_body) return PR_TRUE; // not frame for element // We should only look at the background if we're in an HTML document nsCOMPtr node(do_QueryInterface(content)); nsCOMPtr doc; node->GetOwnerDocument(getter_AddRefs(doc)); nsCOMPtr htmlDoc(do_QueryInterface(doc)); if (!htmlDoc) return PR_TRUE; const nsStyleBackground *htmlBG; ::GetStyleData(parentFrame, &htmlBG); return !htmlBG->BackgroundIsTransparent(); } PRBool nsContextMenuInfo::IsCanvasFrame(nsIFrame *aFrame) { nsCOMPtr mTag_canvasFrame = getter_AddRefs(NS_NewAtom("CanvasFrame")); nsCOMPtr mTag_rootFrame = getter_AddRefs(NS_NewAtom("RootFrame")); nsCOMPtr mTag_pageFrame = getter_AddRefs(NS_NewAtom("PageFrame")); nsCOMPtr frameType; aFrame->GetFrameType(getter_AddRefs(frameType)); return (frameType == mTag_canvasFrame || frameType == mTag_rootFrame || frameType == mTag_pageFrame); } PRBool nsContextMenuInfo::FindBackground(nsIPresContext* aPresContext, nsIFrame* aForFrame, const nsStyleBackground** aBackground, PRBool* aIsCanvas) { *aIsCanvas = PR_TRUE; return FindCanvasBackground(aPresContext, aForFrame, aBackground); }