1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/libeditor/html/nsHTMLAnonymousUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,466 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "mozilla/Attributes.h" 1.9 +#include "mozilla/dom/Element.h" 1.10 +#include "mozilla/mozalloc.h" 1.11 +#include "nsAString.h" 1.12 +#include "nsAutoPtr.h" 1.13 +#include "nsCOMPtr.h" 1.14 +#include "nsComputedDOMStyle.h" 1.15 +#include "nsDebug.h" 1.16 +#include "nsEditProperty.h" 1.17 +#include "nsError.h" 1.18 +#include "nsHTMLCSSUtils.h" 1.19 +#include "nsHTMLEditor.h" 1.20 +#include "nsIAtom.h" 1.21 +#include "nsIContent.h" 1.22 +#include "nsID.h" 1.23 +#include "nsIDOMCSSPrimitiveValue.h" 1.24 +#include "nsIDOMCSSStyleDeclaration.h" 1.25 +#include "nsIDOMCSSValue.h" 1.26 +#include "nsIDOMElement.h" 1.27 +#include "nsIDOMEventTarget.h" 1.28 +#include "nsIDOMHTMLElement.h" 1.29 +#include "nsIDOMNode.h" 1.30 +#include "nsIDOMWindow.h" 1.31 +#include "nsIDocument.h" 1.32 +#include "nsIDocumentObserver.h" 1.33 +#include "nsIHTMLAbsPosEditor.h" 1.34 +#include "nsIHTMLEditor.h" 1.35 +#include "nsIHTMLInlineTableEditor.h" 1.36 +#include "nsIHTMLObjectResizer.h" 1.37 +#include "nsIMutationObserver.h" 1.38 +#include "nsINode.h" 1.39 +#include "nsIPresShell.h" 1.40 +#include "nsISupportsImpl.h" 1.41 +#include "nsISupportsUtils.h" 1.42 +#include "nsLiteralString.h" 1.43 +#include "nsPresContext.h" 1.44 +#include "nsReadableUtils.h" 1.45 +#include "nsString.h" 1.46 +#include "nsStringFwd.h" 1.47 +#include "nsUnicharUtils.h" 1.48 +#include "nscore.h" 1.49 +#include "nsContentUtils.h" // for nsAutoScriptBlocker 1.50 + 1.51 +class nsIDOMEventListener; 1.52 +class nsISelection; 1.53 + 1.54 +using namespace mozilla; 1.55 + 1.56 +// retrieve an integer stored into a CSS computed float value 1.57 +static int32_t GetCSSFloatValue(nsIDOMCSSStyleDeclaration * aDecl, 1.58 + const nsAString & aProperty) 1.59 +{ 1.60 + MOZ_ASSERT(aDecl); 1.61 + 1.62 + nsCOMPtr<nsIDOMCSSValue> value; 1.63 + // get the computed CSSValue of the property 1.64 + nsresult res = aDecl->GetPropertyCSSValue(aProperty, getter_AddRefs(value)); 1.65 + if (NS_FAILED(res) || !value) return 0; 1.66 + 1.67 + // check the type of the returned CSSValue; we handle here only 1.68 + // pixel and enum types 1.69 + nsCOMPtr<nsIDOMCSSPrimitiveValue> val = do_QueryInterface(value); 1.70 + uint16_t type; 1.71 + val->GetPrimitiveType(&type); 1.72 + 1.73 + float f = 0; 1.74 + switch (type) { 1.75 + case nsIDOMCSSPrimitiveValue::CSS_PX: 1.76 + // the value is in pixels, just get it 1.77 + res = val->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX, &f); 1.78 + NS_ENSURE_SUCCESS(res, 0); 1.79 + break; 1.80 + case nsIDOMCSSPrimitiveValue::CSS_IDENT: { 1.81 + // the value is keyword, we have to map these keywords into 1.82 + // numeric values 1.83 + nsAutoString str; 1.84 + res = val->GetStringValue(str); 1.85 + if (str.EqualsLiteral("thin")) 1.86 + f = 1; 1.87 + else if (str.EqualsLiteral("medium")) 1.88 + f = 3; 1.89 + else if (str.EqualsLiteral("thick")) 1.90 + f = 5; 1.91 + break; 1.92 + } 1.93 + } 1.94 + 1.95 + return (int32_t) f; 1.96 +} 1.97 + 1.98 +class nsElementDeletionObserver MOZ_FINAL : public nsIMutationObserver 1.99 +{ 1.100 +public: 1.101 + nsElementDeletionObserver(nsINode* aNativeAnonNode, nsINode* aObservedNode) 1.102 + : mNativeAnonNode(aNativeAnonNode), mObservedNode(aObservedNode) {} 1.103 + NS_DECL_ISUPPORTS 1.104 + NS_DECL_NSIMUTATIONOBSERVER 1.105 +protected: 1.106 + nsINode* mNativeAnonNode; 1.107 + nsINode* mObservedNode; 1.108 +}; 1.109 + 1.110 +NS_IMPL_ISUPPORTS(nsElementDeletionObserver, nsIMutationObserver) 1.111 +NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(nsElementDeletionObserver) 1.112 + 1.113 +void 1.114 +nsElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode) 1.115 +{ 1.116 + NS_ASSERTION(aNode == mNativeAnonNode || aNode == mObservedNode, 1.117 + "Wrong aNode!"); 1.118 + if (aNode == mNativeAnonNode) { 1.119 + mObservedNode->RemoveMutationObserver(this); 1.120 + } else { 1.121 + mNativeAnonNode->RemoveMutationObserver(this); 1.122 + static_cast<nsIContent*>(mNativeAnonNode)->UnbindFromTree(); 1.123 + } 1.124 + 1.125 + NS_RELEASE_THIS(); 1.126 +} 1.127 + 1.128 +// Returns in *aReturn an anonymous nsDOMElement of type aTag, 1.129 +// child of aParentNode. If aIsCreatedHidden is true, the class 1.130 +// "hidden" is added to the created element. If aAnonClass is not 1.131 +// the empty string, it becomes the value of the attribute "_moz_anonclass" 1.132 +nsresult 1.133 +nsHTMLEditor::CreateAnonymousElement(const nsAString & aTag, nsIDOMNode * aParentNode, 1.134 + const nsAString & aAnonClass, bool aIsCreatedHidden, 1.135 + nsIDOMElement ** aReturn) 1.136 +{ 1.137 + NS_ENSURE_ARG_POINTER(aParentNode); 1.138 + NS_ENSURE_ARG_POINTER(aReturn); 1.139 + *aReturn = nullptr; 1.140 + 1.141 + nsCOMPtr<nsIContent> parentContent( do_QueryInterface(aParentNode) ); 1.142 + NS_ENSURE_TRUE(parentContent, NS_OK); 1.143 + 1.144 + nsCOMPtr<nsIDocument> doc = GetDocument(); 1.145 + NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); 1.146 + 1.147 + // Get the pres shell 1.148 + nsCOMPtr<nsIPresShell> ps = GetPresShell(); 1.149 + NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); 1.150 + 1.151 + // Create a new node through the element factory 1.152 + nsCOMPtr<dom::Element> newContent; 1.153 + nsresult res = CreateHTMLContent(aTag, getter_AddRefs(newContent)); 1.154 + NS_ENSURE_SUCCESS(res, res); 1.155 + 1.156 + nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newContent); 1.157 + NS_ENSURE_TRUE(newElement, NS_ERROR_FAILURE); 1.158 + 1.159 + // add the "hidden" class if needed 1.160 + if (aIsCreatedHidden) { 1.161 + res = newElement->SetAttribute(NS_LITERAL_STRING("class"), 1.162 + NS_LITERAL_STRING("hidden")); 1.163 + NS_ENSURE_SUCCESS(res, res); 1.164 + } 1.165 + 1.166 + // add an _moz_anonclass attribute if needed 1.167 + if (!aAnonClass.IsEmpty()) { 1.168 + res = newElement->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"), 1.169 + aAnonClass); 1.170 + NS_ENSURE_SUCCESS(res, res); 1.171 + } 1.172 + 1.173 + { 1.174 + nsAutoScriptBlocker scriptBlocker; 1.175 + 1.176 + // establish parenthood of the element 1.177 + newContent->SetIsNativeAnonymousRoot(); 1.178 + res = newContent->BindToTree(doc, parentContent, parentContent, true); 1.179 + if (NS_FAILED(res)) { 1.180 + newContent->UnbindFromTree(); 1.181 + return res; 1.182 + } 1.183 + } 1.184 + 1.185 + nsElementDeletionObserver* observer = 1.186 + new nsElementDeletionObserver(newContent, parentContent); 1.187 + NS_ADDREF(observer); // NodeWillBeDestroyed releases. 1.188 + parentContent->AddMutationObserver(observer); 1.189 + newContent->AddMutationObserver(observer); 1.190 + 1.191 + // display the element 1.192 + ps->RecreateFramesFor(newContent); 1.193 + 1.194 + newElement.forget(aReturn); 1.195 + return NS_OK; 1.196 +} 1.197 + 1.198 +// Removes event listener and calls DeleteRefToAnonymousNode. 1.199 +void 1.200 +nsHTMLEditor::RemoveListenerAndDeleteRef(const nsAString& aEvent, 1.201 + nsIDOMEventListener* aListener, 1.202 + bool aUseCapture, 1.203 + nsIDOMElement* aElement, 1.204 + nsIContent * aParentContent, 1.205 + nsIPresShell* aShell) 1.206 +{ 1.207 + nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(aElement)); 1.208 + if (evtTarget) { 1.209 + evtTarget->RemoveEventListener(aEvent, aListener, aUseCapture); 1.210 + } 1.211 + DeleteRefToAnonymousNode(aElement, aParentContent, aShell); 1.212 +} 1.213 + 1.214 +// Deletes all references to an anonymous element 1.215 +void 1.216 +nsHTMLEditor::DeleteRefToAnonymousNode(nsIDOMElement* aElement, 1.217 + nsIContent* aParentContent, 1.218 + nsIPresShell* aShell) 1.219 +{ 1.220 + // call ContentRemoved() for the anonymous content 1.221 + // node so its references get removed from the frame manager's 1.222 + // undisplay map, and its layout frames get destroyed! 1.223 + 1.224 + if (aElement) { 1.225 + nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); 1.226 + if (content) { 1.227 + nsAutoScriptBlocker scriptBlocker; 1.228 + // Need to check whether aShell has been destroyed (but not yet deleted). 1.229 + // In that case presContext->GetPresShell() returns nullptr. 1.230 + // See bug 338129. 1.231 + if (aShell && aShell->GetPresContext() && 1.232 + aShell->GetPresContext()->GetPresShell() == aShell) { 1.233 + nsCOMPtr<nsIDocumentObserver> docObserver = do_QueryInterface(aShell); 1.234 + if (docObserver) { 1.235 + // Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell 1.236 + // knows we're messing with the frame tree. 1.237 + nsCOMPtr<nsIDocument> document = GetDocument(); 1.238 + if (document) 1.239 + docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL); 1.240 + 1.241 + // XXX This is wrong (bug 439258). Once it's fixed, the NS_WARNING 1.242 + // in RestyleManager::RestyleForRemove should be changed back 1.243 + // to an assertion. 1.244 + docObserver->ContentRemoved(content->GetCurrentDoc(), 1.245 + aParentContent, content, -1, 1.246 + content->GetPreviousSibling()); 1.247 + if (document) 1.248 + docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL); 1.249 + } 1.250 + } 1.251 + content->UnbindFromTree(); 1.252 + } 1.253 + } 1.254 +} 1.255 + 1.256 +// The following method is mostly called by a selection listener. When a 1.257 +// selection change is notified, the method is called to check if resizing 1.258 +// handles, a grabber and/or inline table editing UI need to be displayed 1.259 +// or refreshed 1.260 +NS_IMETHODIMP 1.261 +nsHTMLEditor::CheckSelectionStateForAnonymousButtons(nsISelection * aSelection) 1.262 +{ 1.263 + NS_ENSURE_ARG_POINTER(aSelection); 1.264 + 1.265 + // early way out if all contextual UI extensions are disabled 1.266 + NS_ENSURE_TRUE(mIsObjectResizingEnabled || 1.267 + mIsAbsolutelyPositioningEnabled || 1.268 + mIsInlineTableEditingEnabled, NS_OK); 1.269 + 1.270 + // Don't change selection state if we're moving. 1.271 + if (mIsMoving) { 1.272 + return NS_OK; 1.273 + } 1.274 + 1.275 + nsCOMPtr<nsIDOMElement> focusElement; 1.276 + // let's get the containing element of the selection 1.277 + nsresult res = GetSelectionContainer(getter_AddRefs(focusElement)); 1.278 + NS_ENSURE_TRUE(focusElement, NS_OK); 1.279 + NS_ENSURE_SUCCESS(res, res); 1.280 + 1.281 + // If we're not in a document, don't try to add resizers 1.282 + nsCOMPtr<dom::Element> focusElementNode = do_QueryInterface(focusElement); 1.283 + NS_ENSURE_STATE(focusElementNode); 1.284 + if (!focusElementNode->IsInDoc()) { 1.285 + return NS_OK; 1.286 + } 1.287 + 1.288 + // what's its tag? 1.289 + nsAutoString focusTagName; 1.290 + res = focusElement->GetTagName(focusTagName); 1.291 + NS_ENSURE_SUCCESS(res, res); 1.292 + ToLowerCase(focusTagName); 1.293 + nsCOMPtr<nsIAtom> focusTagAtom = do_GetAtom(focusTagName); 1.294 + 1.295 + nsCOMPtr<nsIDOMElement> absPosElement; 1.296 + if (mIsAbsolutelyPositioningEnabled) { 1.297 + // Absolute Positioning support is enabled, is the selection contained 1.298 + // in an absolutely positioned element ? 1.299 + res = GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(absPosElement)); 1.300 + NS_ENSURE_SUCCESS(res, res); 1.301 + } 1.302 + 1.303 + nsCOMPtr<nsIDOMElement> cellElement; 1.304 + if (mIsObjectResizingEnabled || mIsInlineTableEditingEnabled) { 1.305 + // Resizing or Inline Table Editing is enabled, we need to check if the 1.306 + // selection is contained in a table cell 1.307 + res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), 1.308 + nullptr, 1.309 + getter_AddRefs(cellElement)); 1.310 + NS_ENSURE_SUCCESS(res, res); 1.311 + } 1.312 + 1.313 + if (mIsObjectResizingEnabled && cellElement) { 1.314 + // we are here because Resizing is enabled AND selection is contained in 1.315 + // a cell 1.316 + 1.317 + // get the enclosing table 1.318 + if (nsEditProperty::img != focusTagAtom) { 1.319 + // the element container of the selection is not an image, so we'll show 1.320 + // the resizers around the table 1.321 + nsCOMPtr<nsIDOMNode> tableNode = GetEnclosingTable(cellElement); 1.322 + focusElement = do_QueryInterface(tableNode); 1.323 + focusTagAtom = nsEditProperty::table; 1.324 + } 1.325 + } 1.326 + 1.327 + // we allow resizers only around images, tables, and absolutely positioned 1.328 + // elements. If we don't have image/table, let's look at the latter case. 1.329 + if (nsEditProperty::img != focusTagAtom && 1.330 + nsEditProperty::table != focusTagAtom) 1.331 + focusElement = absPosElement; 1.332 + 1.333 + // at this point, focusElement contains the element for Resizing, 1.334 + // cellElement contains the element for InlineTableEditing 1.335 + // absPosElement contains the element for Positioning 1.336 + 1.337 + // Note: All the Hide/Show methods below may change attributes on real 1.338 + // content which means a DOMAttrModified handler may cause arbitrary 1.339 + // side effects while this code runs (bug 420439). 1.340 + 1.341 + if (mIsAbsolutelyPositioningEnabled && mAbsolutelyPositionedObject && 1.342 + absPosElement != mAbsolutelyPositionedObject) { 1.343 + res = HideGrabber(); 1.344 + NS_ENSURE_SUCCESS(res, res); 1.345 + NS_ASSERTION(!mAbsolutelyPositionedObject, "HideGrabber failed"); 1.346 + } 1.347 + 1.348 + if (mIsObjectResizingEnabled && mResizedObject && 1.349 + mResizedObject != focusElement) { 1.350 + res = HideResizers(); 1.351 + NS_ENSURE_SUCCESS(res, res); 1.352 + NS_ASSERTION(!mResizedObject, "HideResizers failed"); 1.353 + } 1.354 + 1.355 + if (mIsInlineTableEditingEnabled && mInlineEditedCell && 1.356 + mInlineEditedCell != cellElement) { 1.357 + res = HideInlineTableEditingUI(); 1.358 + NS_ENSURE_SUCCESS(res, res); 1.359 + NS_ASSERTION(!mInlineEditedCell, "HideInlineTableEditingUI failed"); 1.360 + } 1.361 + 1.362 + // now, let's display all contextual UI for good 1.363 + nsIContent* hostContent = GetActiveEditingHost(); 1.364 + nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent); 1.365 + 1.366 + if (mIsObjectResizingEnabled && focusElement && 1.367 + IsModifiableNode(focusElement) && focusElement != hostNode) { 1.368 + if (nsEditProperty::img == focusTagAtom) 1.369 + mResizedObjectIsAnImage = true; 1.370 + if (mResizedObject) 1.371 + res = RefreshResizers(); 1.372 + else 1.373 + res = ShowResizers(focusElement); 1.374 + NS_ENSURE_SUCCESS(res, res); 1.375 + } 1.376 + 1.377 + if (mIsAbsolutelyPositioningEnabled && absPosElement && 1.378 + IsModifiableNode(absPosElement) && absPosElement != hostNode) { 1.379 + if (mAbsolutelyPositionedObject) 1.380 + res = RefreshGrabber(); 1.381 + else 1.382 + res = ShowGrabberOnElement(absPosElement); 1.383 + NS_ENSURE_SUCCESS(res, res); 1.384 + } 1.385 + 1.386 + if (mIsInlineTableEditingEnabled && cellElement && 1.387 + IsModifiableNode(cellElement) && cellElement != hostNode) { 1.388 + if (mInlineEditedCell) 1.389 + res = RefreshInlineTableEditingUI(); 1.390 + else 1.391 + res = ShowInlineTableEditingUI(cellElement); 1.392 + } 1.393 + 1.394 + return res; 1.395 +} 1.396 + 1.397 +// Resizing and Absolute Positioning need to know everything about the 1.398 +// containing box of the element: position, size, margins, borders 1.399 +nsresult 1.400 +nsHTMLEditor::GetPositionAndDimensions(nsIDOMElement * aElement, 1.401 + int32_t & aX, int32_t & aY, 1.402 + int32_t & aW, int32_t & aH, 1.403 + int32_t & aBorderLeft, 1.404 + int32_t & aBorderTop, 1.405 + int32_t & aMarginLeft, 1.406 + int32_t & aMarginTop) 1.407 +{ 1.408 + NS_ENSURE_ARG_POINTER(aElement); 1.409 + 1.410 + // Is the element positioned ? let's check the cheap way first... 1.411 + bool isPositioned = false; 1.412 + nsresult res = aElement->HasAttribute(NS_LITERAL_STRING("_moz_abspos"), &isPositioned); 1.413 + NS_ENSURE_SUCCESS(res, res); 1.414 + if (!isPositioned) { 1.415 + // hmmm... the expensive way now... 1.416 + nsAutoString positionStr; 1.417 + mHTMLCSSUtils->GetComputedProperty(aElement, nsEditProperty::cssPosition, 1.418 + positionStr); 1.419 + isPositioned = positionStr.EqualsLiteral("absolute"); 1.420 + } 1.421 + 1.422 + if (isPositioned) { 1.423 + // Yes, it is absolutely positioned 1.424 + mResizedObjectIsAbsolutelyPositioned = true; 1.425 + 1.426 + // Get the all the computed css styles attached to the element node 1.427 + nsRefPtr<nsComputedDOMStyle> cssDecl = 1.428 + mHTMLCSSUtils->GetComputedStyle(aElement); 1.429 + NS_ENSURE_STATE(cssDecl); 1.430 + 1.431 + aBorderLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-left-width")); 1.432 + aBorderTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-top-width")); 1.433 + aMarginLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-left")); 1.434 + aMarginTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-top")); 1.435 + 1.436 + aX = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("left")) + 1.437 + aMarginLeft + aBorderLeft; 1.438 + aY = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("top")) + 1.439 + aMarginTop + aBorderTop; 1.440 + aW = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("width")); 1.441 + aH = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("height")); 1.442 + } 1.443 + else { 1.444 + mResizedObjectIsAbsolutelyPositioned = false; 1.445 + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aElement); 1.446 + if (!htmlElement) { 1.447 + return NS_ERROR_NULL_POINTER; 1.448 + } 1.449 + GetElementOrigin(aElement, aX, aY); 1.450 + 1.451 + res = htmlElement->GetOffsetWidth(&aW); 1.452 + NS_ENSURE_SUCCESS(res, res); 1.453 + res = htmlElement->GetOffsetHeight(&aH); 1.454 + 1.455 + aBorderLeft = 0; 1.456 + aBorderTop = 0; 1.457 + aMarginLeft = 0; 1.458 + aMarginTop = 0; 1.459 + } 1.460 + return res; 1.461 +} 1.462 + 1.463 +// self-explanatory 1.464 +void 1.465 +nsHTMLEditor::SetAnonymousElementPosition(int32_t aX, int32_t aY, nsIDOMElement *aElement) 1.466 +{ 1.467 + mHTMLCSSUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("left"), aX); 1.468 + mHTMLCSSUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("top"), aY); 1.469 +}