michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsComputedDOMStyle.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditProperty.h" michael@0: #include "nsError.h" michael@0: #include "nsHTMLCSSUtils.h" michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIContent.h" michael@0: #include "nsID.h" michael@0: #include "nsIDOMCSSPrimitiveValue.h" michael@0: #include "nsIDOMCSSStyleDeclaration.h" michael@0: #include "nsIDOMCSSValue.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMEventTarget.h" michael@0: #include "nsIDOMHTMLElement.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDocumentObserver.h" michael@0: #include "nsIHTMLAbsPosEditor.h" michael@0: #include "nsIHTMLEditor.h" michael@0: #include "nsIHTMLInlineTableEditor.h" michael@0: #include "nsIHTMLObjectResizer.h" michael@0: #include "nsIMutationObserver.h" michael@0: #include "nsINode.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsStringFwd.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nscore.h" michael@0: #include "nsContentUtils.h" // for nsAutoScriptBlocker michael@0: michael@0: class nsIDOMEventListener; michael@0: class nsISelection; michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // retrieve an integer stored into a CSS computed float value michael@0: static int32_t GetCSSFloatValue(nsIDOMCSSStyleDeclaration * aDecl, michael@0: const nsAString & aProperty) michael@0: { michael@0: MOZ_ASSERT(aDecl); michael@0: michael@0: nsCOMPtr value; michael@0: // get the computed CSSValue of the property michael@0: nsresult res = aDecl->GetPropertyCSSValue(aProperty, getter_AddRefs(value)); michael@0: if (NS_FAILED(res) || !value) return 0; michael@0: michael@0: // check the type of the returned CSSValue; we handle here only michael@0: // pixel and enum types michael@0: nsCOMPtr val = do_QueryInterface(value); michael@0: uint16_t type; michael@0: val->GetPrimitiveType(&type); michael@0: michael@0: float f = 0; michael@0: switch (type) { michael@0: case nsIDOMCSSPrimitiveValue::CSS_PX: michael@0: // the value is in pixels, just get it michael@0: res = val->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX, &f); michael@0: NS_ENSURE_SUCCESS(res, 0); michael@0: break; michael@0: case nsIDOMCSSPrimitiveValue::CSS_IDENT: { michael@0: // the value is keyword, we have to map these keywords into michael@0: // numeric values michael@0: nsAutoString str; michael@0: res = val->GetStringValue(str); michael@0: if (str.EqualsLiteral("thin")) michael@0: f = 1; michael@0: else if (str.EqualsLiteral("medium")) michael@0: f = 3; michael@0: else if (str.EqualsLiteral("thick")) michael@0: f = 5; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return (int32_t) f; michael@0: } michael@0: michael@0: class nsElementDeletionObserver MOZ_FINAL : public nsIMutationObserver michael@0: { michael@0: public: michael@0: nsElementDeletionObserver(nsINode* aNativeAnonNode, nsINode* aObservedNode) michael@0: : mNativeAnonNode(aNativeAnonNode), mObservedNode(aObservedNode) {} michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIMUTATIONOBSERVER michael@0: protected: michael@0: nsINode* mNativeAnonNode; michael@0: nsINode* mObservedNode; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsElementDeletionObserver, nsIMutationObserver) michael@0: NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(nsElementDeletionObserver) michael@0: michael@0: void michael@0: nsElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode) michael@0: { michael@0: NS_ASSERTION(aNode == mNativeAnonNode || aNode == mObservedNode, michael@0: "Wrong aNode!"); michael@0: if (aNode == mNativeAnonNode) { michael@0: mObservedNode->RemoveMutationObserver(this); michael@0: } else { michael@0: mNativeAnonNode->RemoveMutationObserver(this); michael@0: static_cast(mNativeAnonNode)->UnbindFromTree(); michael@0: } michael@0: michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: michael@0: // Returns in *aReturn an anonymous nsDOMElement of type aTag, michael@0: // child of aParentNode. If aIsCreatedHidden is true, the class michael@0: // "hidden" is added to the created element. If aAnonClass is not michael@0: // the empty string, it becomes the value of the attribute "_moz_anonclass" michael@0: nsresult michael@0: nsHTMLEditor::CreateAnonymousElement(const nsAString & aTag, nsIDOMNode * aParentNode, michael@0: const nsAString & aAnonClass, bool aIsCreatedHidden, michael@0: nsIDOMElement ** aReturn) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aParentNode); michael@0: NS_ENSURE_ARG_POINTER(aReturn); michael@0: *aReturn = nullptr; michael@0: michael@0: nsCOMPtr parentContent( do_QueryInterface(aParentNode) ); michael@0: NS_ENSURE_TRUE(parentContent, NS_OK); michael@0: michael@0: nsCOMPtr doc = GetDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Get the pres shell michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Create a new node through the element factory michael@0: nsCOMPtr newContent; michael@0: nsresult res = CreateHTMLContent(aTag, getter_AddRefs(newContent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr newElement = do_QueryInterface(newContent); michael@0: NS_ENSURE_TRUE(newElement, NS_ERROR_FAILURE); michael@0: michael@0: // add the "hidden" class if needed michael@0: if (aIsCreatedHidden) { michael@0: res = newElement->SetAttribute(NS_LITERAL_STRING("class"), michael@0: NS_LITERAL_STRING("hidden")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // add an _moz_anonclass attribute if needed michael@0: if (!aAnonClass.IsEmpty()) { michael@0: res = newElement->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"), michael@0: aAnonClass); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: michael@0: // establish parenthood of the element michael@0: newContent->SetIsNativeAnonymousRoot(); michael@0: res = newContent->BindToTree(doc, parentContent, parentContent, true); michael@0: if (NS_FAILED(res)) { michael@0: newContent->UnbindFromTree(); michael@0: return res; michael@0: } michael@0: } michael@0: michael@0: nsElementDeletionObserver* observer = michael@0: new nsElementDeletionObserver(newContent, parentContent); michael@0: NS_ADDREF(observer); // NodeWillBeDestroyed releases. michael@0: parentContent->AddMutationObserver(observer); michael@0: newContent->AddMutationObserver(observer); michael@0: michael@0: // display the element michael@0: ps->RecreateFramesFor(newContent); michael@0: michael@0: newElement.forget(aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Removes event listener and calls DeleteRefToAnonymousNode. michael@0: void michael@0: nsHTMLEditor::RemoveListenerAndDeleteRef(const nsAString& aEvent, michael@0: nsIDOMEventListener* aListener, michael@0: bool aUseCapture, michael@0: nsIDOMElement* aElement, michael@0: nsIContent * aParentContent, michael@0: nsIPresShell* aShell) michael@0: { michael@0: nsCOMPtr evtTarget(do_QueryInterface(aElement)); michael@0: if (evtTarget) { michael@0: evtTarget->RemoveEventListener(aEvent, aListener, aUseCapture); michael@0: } michael@0: DeleteRefToAnonymousNode(aElement, aParentContent, aShell); michael@0: } michael@0: michael@0: // Deletes all references to an anonymous element michael@0: void michael@0: nsHTMLEditor::DeleteRefToAnonymousNode(nsIDOMElement* aElement, michael@0: nsIContent* aParentContent, michael@0: nsIPresShell* aShell) michael@0: { michael@0: // call ContentRemoved() for the anonymous content michael@0: // node so its references get removed from the frame manager's michael@0: // undisplay map, and its layout frames get destroyed! michael@0: michael@0: if (aElement) { michael@0: nsCOMPtr content = do_QueryInterface(aElement); michael@0: if (content) { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: // Need to check whether aShell has been destroyed (but not yet deleted). michael@0: // In that case presContext->GetPresShell() returns nullptr. michael@0: // See bug 338129. michael@0: if (aShell && aShell->GetPresContext() && michael@0: aShell->GetPresContext()->GetPresShell() == aShell) { michael@0: nsCOMPtr docObserver = do_QueryInterface(aShell); michael@0: if (docObserver) { michael@0: // Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell michael@0: // knows we're messing with the frame tree. michael@0: nsCOMPtr document = GetDocument(); michael@0: if (document) michael@0: docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL); michael@0: michael@0: // XXX This is wrong (bug 439258). Once it's fixed, the NS_WARNING michael@0: // in RestyleManager::RestyleForRemove should be changed back michael@0: // to an assertion. michael@0: docObserver->ContentRemoved(content->GetCurrentDoc(), michael@0: aParentContent, content, -1, michael@0: content->GetPreviousSibling()); michael@0: if (document) michael@0: docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL); michael@0: } michael@0: } michael@0: content->UnbindFromTree(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The following method is mostly called by a selection listener. When a michael@0: // selection change is notified, the method is called to check if resizing michael@0: // handles, a grabber and/or inline table editing UI need to be displayed michael@0: // or refreshed michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::CheckSelectionStateForAnonymousButtons(nsISelection * aSelection) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aSelection); michael@0: michael@0: // early way out if all contextual UI extensions are disabled michael@0: NS_ENSURE_TRUE(mIsObjectResizingEnabled || michael@0: mIsAbsolutelyPositioningEnabled || michael@0: mIsInlineTableEditingEnabled, NS_OK); michael@0: michael@0: // Don't change selection state if we're moving. michael@0: if (mIsMoving) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr focusElement; michael@0: // let's get the containing element of the selection michael@0: nsresult res = GetSelectionContainer(getter_AddRefs(focusElement)); michael@0: NS_ENSURE_TRUE(focusElement, NS_OK); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // If we're not in a document, don't try to add resizers michael@0: nsCOMPtr focusElementNode = do_QueryInterface(focusElement); michael@0: NS_ENSURE_STATE(focusElementNode); michael@0: if (!focusElementNode->IsInDoc()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // what's its tag? michael@0: nsAutoString focusTagName; michael@0: res = focusElement->GetTagName(focusTagName); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: ToLowerCase(focusTagName); michael@0: nsCOMPtr focusTagAtom = do_GetAtom(focusTagName); michael@0: michael@0: nsCOMPtr absPosElement; michael@0: if (mIsAbsolutelyPositioningEnabled) { michael@0: // Absolute Positioning support is enabled, is the selection contained michael@0: // in an absolutely positioned element ? michael@0: res = GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(absPosElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: nsCOMPtr cellElement; michael@0: if (mIsObjectResizingEnabled || mIsInlineTableEditingEnabled) { michael@0: // Resizing or Inline Table Editing is enabled, we need to check if the michael@0: // selection is contained in a table cell michael@0: res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), michael@0: nullptr, michael@0: getter_AddRefs(cellElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (mIsObjectResizingEnabled && cellElement) { michael@0: // we are here because Resizing is enabled AND selection is contained in michael@0: // a cell michael@0: michael@0: // get the enclosing table michael@0: if (nsEditProperty::img != focusTagAtom) { michael@0: // the element container of the selection is not an image, so we'll show michael@0: // the resizers around the table michael@0: nsCOMPtr tableNode = GetEnclosingTable(cellElement); michael@0: focusElement = do_QueryInterface(tableNode); michael@0: focusTagAtom = nsEditProperty::table; michael@0: } michael@0: } michael@0: michael@0: // we allow resizers only around images, tables, and absolutely positioned michael@0: // elements. If we don't have image/table, let's look at the latter case. michael@0: if (nsEditProperty::img != focusTagAtom && michael@0: nsEditProperty::table != focusTagAtom) michael@0: focusElement = absPosElement; michael@0: michael@0: // at this point, focusElement contains the element for Resizing, michael@0: // cellElement contains the element for InlineTableEditing michael@0: // absPosElement contains the element for Positioning michael@0: michael@0: // Note: All the Hide/Show methods below may change attributes on real michael@0: // content which means a DOMAttrModified handler may cause arbitrary michael@0: // side effects while this code runs (bug 420439). michael@0: michael@0: if (mIsAbsolutelyPositioningEnabled && mAbsolutelyPositionedObject && michael@0: absPosElement != mAbsolutelyPositionedObject) { michael@0: res = HideGrabber(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ASSERTION(!mAbsolutelyPositionedObject, "HideGrabber failed"); michael@0: } michael@0: michael@0: if (mIsObjectResizingEnabled && mResizedObject && michael@0: mResizedObject != focusElement) { michael@0: res = HideResizers(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ASSERTION(!mResizedObject, "HideResizers failed"); michael@0: } michael@0: michael@0: if (mIsInlineTableEditingEnabled && mInlineEditedCell && michael@0: mInlineEditedCell != cellElement) { michael@0: res = HideInlineTableEditingUI(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ASSERTION(!mInlineEditedCell, "HideInlineTableEditingUI failed"); michael@0: } michael@0: michael@0: // now, let's display all contextual UI for good michael@0: nsIContent* hostContent = GetActiveEditingHost(); michael@0: nsCOMPtr hostNode = do_QueryInterface(hostContent); michael@0: michael@0: if (mIsObjectResizingEnabled && focusElement && michael@0: IsModifiableNode(focusElement) && focusElement != hostNode) { michael@0: if (nsEditProperty::img == focusTagAtom) michael@0: mResizedObjectIsAnImage = true; michael@0: if (mResizedObject) michael@0: res = RefreshResizers(); michael@0: else michael@0: res = ShowResizers(focusElement); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (mIsAbsolutelyPositioningEnabled && absPosElement && michael@0: IsModifiableNode(absPosElement) && absPosElement != hostNode) { michael@0: if (mAbsolutelyPositionedObject) michael@0: res = RefreshGrabber(); michael@0: else michael@0: res = ShowGrabberOnElement(absPosElement); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (mIsInlineTableEditingEnabled && cellElement && michael@0: IsModifiableNode(cellElement) && cellElement != hostNode) { michael@0: if (mInlineEditedCell) michael@0: res = RefreshInlineTableEditingUI(); michael@0: else michael@0: res = ShowInlineTableEditingUI(cellElement); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: // Resizing and Absolute Positioning need to know everything about the michael@0: // containing box of the element: position, size, margins, borders michael@0: nsresult michael@0: nsHTMLEditor::GetPositionAndDimensions(nsIDOMElement * aElement, michael@0: int32_t & aX, int32_t & aY, michael@0: int32_t & aW, int32_t & aH, michael@0: int32_t & aBorderLeft, michael@0: int32_t & aBorderTop, michael@0: int32_t & aMarginLeft, michael@0: int32_t & aMarginTop) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aElement); michael@0: michael@0: // Is the element positioned ? let's check the cheap way first... michael@0: bool isPositioned = false; michael@0: nsresult res = aElement->HasAttribute(NS_LITERAL_STRING("_moz_abspos"), &isPositioned); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!isPositioned) { michael@0: // hmmm... the expensive way now... michael@0: nsAutoString positionStr; michael@0: mHTMLCSSUtils->GetComputedProperty(aElement, nsEditProperty::cssPosition, michael@0: positionStr); michael@0: isPositioned = positionStr.EqualsLiteral("absolute"); michael@0: } michael@0: michael@0: if (isPositioned) { michael@0: // Yes, it is absolutely positioned michael@0: mResizedObjectIsAbsolutelyPositioned = true; michael@0: michael@0: // Get the all the computed css styles attached to the element node michael@0: nsRefPtr cssDecl = michael@0: mHTMLCSSUtils->GetComputedStyle(aElement); michael@0: NS_ENSURE_STATE(cssDecl); michael@0: michael@0: aBorderLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-left-width")); michael@0: aBorderTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-top-width")); michael@0: aMarginLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-left")); michael@0: aMarginTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-top")); michael@0: michael@0: aX = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("left")) + michael@0: aMarginLeft + aBorderLeft; michael@0: aY = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("top")) + michael@0: aMarginTop + aBorderTop; michael@0: aW = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("width")); michael@0: aH = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("height")); michael@0: } michael@0: else { michael@0: mResizedObjectIsAbsolutelyPositioned = false; michael@0: nsCOMPtr htmlElement = do_QueryInterface(aElement); michael@0: if (!htmlElement) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: GetElementOrigin(aElement, aX, aY); michael@0: michael@0: res = htmlElement->GetOffsetWidth(&aW); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = htmlElement->GetOffsetHeight(&aH); michael@0: michael@0: aBorderLeft = 0; michael@0: aBorderTop = 0; michael@0: aMarginLeft = 0; michael@0: aMarginTop = 0; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: // self-explanatory michael@0: void michael@0: nsHTMLEditor::SetAnonymousElementPosition(int32_t aX, int32_t aY, nsIDOMElement *aElement) michael@0: { michael@0: mHTMLCSSUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("left"), aX); michael@0: mHTMLCSSUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("top"), aY); michael@0: }