1 : /* ***** BEGIN LICENSE BLOCK *****
2 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 : *
4 : * The contents of this file are subject to the Mozilla Public License Version
5 : * 1.1 (the "License"); you may not use this file except in compliance with
6 : * the License. You may obtain a copy of the License at
7 : * http://www.mozilla.org/MPL/
8 : *
9 : * Software distributed under the License is distributed on an "AS IS" basis,
10 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 : * for the specific language governing rights and limitations under the
12 : * License.
13 : *
14 : * The Original Code is Mozilla.org.
15 : *
16 : * The Initial Developer of the Original Code is
17 : * Netscape Communications Corp.
18 : * Portions created by the Initial Developer are Copyright (C) 2003
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Daniel Glazman (glazman@netscape.com) (Original author)
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsHTMLEditor.h"
39 :
40 : #include "nsIContent.h"
41 : #include "nsIDocument.h"
42 : #include "nsIEditor.h"
43 : #include "nsIPresShell.h"
44 : #include "nsPresContext.h"
45 :
46 : #include "nsISelection.h"
47 :
48 : #include "nsTextEditUtils.h"
49 : #include "nsEditorUtils.h"
50 : #include "nsHTMLEditUtils.h"
51 : #include "nsTextEditRules.h"
52 :
53 : #include "nsIDOMHTMLElement.h"
54 : #include "nsIDOMEventTarget.h"
55 :
56 : #include "nsIDOMCSSValue.h"
57 : #include "nsIDOMCSSPrimitiveValue.h"
58 : #include "nsIDOMCSSStyleDeclaration.h"
59 : #include "nsIMutationObserver.h"
60 : #include "nsUnicharUtils.h"
61 : #include "nsContentUtils.h"
62 :
63 : // retrieve an integer stored into a CSS computed float value
64 0 : static PRInt32 GetCSSFloatValue(nsIDOMCSSStyleDeclaration * aDecl,
65 : const nsAString & aProperty)
66 : {
67 0 : NS_ENSURE_ARG_POINTER(aDecl);
68 :
69 0 : nsCOMPtr<nsIDOMCSSValue> value;
70 : // get the computed CSSValue of the property
71 0 : nsresult res = aDecl->GetPropertyCSSValue(aProperty, getter_AddRefs(value));
72 0 : if (NS_FAILED(res) || !value) return 0;
73 :
74 : // check the type of the returned CSSValue; we handle here only
75 : // pixel and enum types
76 0 : nsCOMPtr<nsIDOMCSSPrimitiveValue> val = do_QueryInterface(value);
77 : PRUint16 type;
78 0 : val->GetPrimitiveType(&type);
79 :
80 0 : float f = 0;
81 0 : switch (type) {
82 : case nsIDOMCSSPrimitiveValue::CSS_PX:
83 : // the value is in pixels, just get it
84 0 : res = val->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX, &f);
85 0 : NS_ENSURE_SUCCESS(res, 0);
86 0 : break;
87 : case nsIDOMCSSPrimitiveValue::CSS_IDENT: {
88 : // the value is keyword, we have to map these keywords into
89 : // numeric values
90 0 : nsAutoString str;
91 0 : res = val->GetStringValue(str);
92 0 : if (str.EqualsLiteral("thin"))
93 0 : f = 1;
94 0 : else if (str.EqualsLiteral("medium"))
95 0 : f = 3;
96 0 : else if (str.EqualsLiteral("thick"))
97 0 : f = 5;
98 : break;
99 : }
100 : }
101 :
102 0 : return (PRInt32) f;
103 : }
104 :
105 : class nsElementDeletionObserver MOZ_FINAL : public nsIMutationObserver
106 : {
107 : public:
108 0 : nsElementDeletionObserver(nsINode* aNativeAnonNode, nsINode* aObservedNode)
109 0 : : mNativeAnonNode(aNativeAnonNode), mObservedNode(aObservedNode) {}
110 : NS_DECL_ISUPPORTS
111 : NS_DECL_NSIMUTATIONOBSERVER
112 : protected:
113 : nsINode* mNativeAnonNode;
114 : nsINode* mObservedNode;
115 : };
116 :
117 0 : NS_IMPL_ISUPPORTS1(nsElementDeletionObserver, nsIMutationObserver)
118 0 : NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(nsElementDeletionObserver)
119 :
120 : void
121 0 : nsElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode)
122 : {
123 0 : NS_ASSERTION(aNode == mNativeAnonNode || aNode == mObservedNode,
124 : "Wrong aNode!");
125 0 : if (aNode == mNativeAnonNode) {
126 0 : mObservedNode->RemoveMutationObserver(this);
127 : } else {
128 0 : mNativeAnonNode->RemoveMutationObserver(this);
129 0 : static_cast<nsIContent*>(mNativeAnonNode)->UnbindFromTree();
130 : }
131 :
132 0 : NS_RELEASE_THIS();
133 0 : }
134 :
135 : // Returns in *aReturn an anonymous nsDOMElement of type aTag,
136 : // child of aParentNode. If aIsCreatedHidden is true, the class
137 : // "hidden" is added to the created element. If aAnonClass is not
138 : // the empty string, it becomes the value of the attribute "_moz_anonclass"
139 : nsresult
140 0 : nsHTMLEditor::CreateAnonymousElement(const nsAString & aTag, nsIDOMNode * aParentNode,
141 : const nsAString & aAnonClass, bool aIsCreatedHidden,
142 : nsIDOMElement ** aReturn)
143 : {
144 0 : NS_ENSURE_ARG_POINTER(aParentNode);
145 0 : NS_ENSURE_ARG_POINTER(aReturn);
146 0 : *aReturn = nsnull;
147 :
148 0 : nsCOMPtr<nsIContent> parentContent( do_QueryInterface(aParentNode) );
149 0 : NS_ENSURE_TRUE(parentContent, NS_OK);
150 :
151 : // Get the document
152 0 : nsCOMPtr<nsIDOMDocument> domDoc;
153 0 : GetDocument(getter_AddRefs(domDoc));
154 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
155 0 : NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
156 :
157 : // Get the pres shell
158 0 : nsCOMPtr<nsIPresShell> ps = GetPresShell();
159 0 : NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
160 :
161 : // Create a new node through the element factory
162 0 : nsCOMPtr<nsIContent> newContent;
163 0 : nsresult res = CreateHTMLContent(aTag, getter_AddRefs(newContent));
164 0 : NS_ENSURE_SUCCESS(res, res);
165 :
166 0 : nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newContent);
167 0 : NS_ENSURE_TRUE(newElement, NS_ERROR_FAILURE);
168 :
169 : // add the "hidden" class if needed
170 0 : if (aIsCreatedHidden) {
171 0 : res = newElement->SetAttribute(NS_LITERAL_STRING("class"),
172 0 : NS_LITERAL_STRING("hidden"));
173 0 : NS_ENSURE_SUCCESS(res, res);
174 : }
175 :
176 : // add an _moz_anonclass attribute if needed
177 0 : if (!aAnonClass.IsEmpty()) {
178 0 : res = newElement->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"),
179 0 : aAnonClass);
180 0 : NS_ENSURE_SUCCESS(res, res);
181 : }
182 :
183 : {
184 0 : nsAutoScriptBlocker scriptBlocker;
185 :
186 : // establish parenthood of the element
187 0 : newContent->SetNativeAnonymous();
188 0 : res = newContent->BindToTree(doc, parentContent, parentContent, true);
189 0 : if (NS_FAILED(res)) {
190 0 : newContent->UnbindFromTree();
191 0 : return res;
192 : }
193 : }
194 :
195 : nsElementDeletionObserver* observer =
196 0 : new nsElementDeletionObserver(newContent, parentContent);
197 0 : if (!observer) {
198 0 : newContent->UnbindFromTree();
199 0 : return NS_ERROR_OUT_OF_MEMORY;
200 : }
201 0 : NS_ADDREF(observer); // NodeWillBeDestroyed releases.
202 0 : parentContent->AddMutationObserver(observer);
203 0 : newContent->AddMutationObserver(observer);
204 :
205 : // display the element
206 0 : ps->RecreateFramesFor(newContent);
207 :
208 0 : *aReturn = newElement;
209 0 : NS_IF_ADDREF(*aReturn);
210 0 : return NS_OK;
211 : }
212 :
213 : // Removes event listener and calls DeleteRefToAnonymousNode.
214 : void
215 0 : nsHTMLEditor::RemoveListenerAndDeleteRef(const nsAString& aEvent,
216 : nsIDOMEventListener* aListener,
217 : bool aUseCapture,
218 : nsIDOMElement* aElement,
219 : nsIContent * aParentContent,
220 : nsIPresShell* aShell)
221 : {
222 0 : nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(aElement));
223 0 : if (evtTarget) {
224 0 : evtTarget->RemoveEventListener(aEvent, aListener, aUseCapture);
225 : }
226 0 : DeleteRefToAnonymousNode(aElement, aParentContent, aShell);
227 0 : }
228 :
229 : // Deletes all references to an anonymous element
230 : void
231 0 : nsHTMLEditor::DeleteRefToAnonymousNode(nsIDOMElement* aElement,
232 : nsIContent* aParentContent,
233 : nsIPresShell* aShell)
234 : {
235 : // call ContentRemoved() for the anonymous content
236 : // node so its references get removed from the frame manager's
237 : // undisplay map, and its layout frames get destroyed!
238 :
239 0 : if (aElement) {
240 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
241 0 : if (content) {
242 0 : nsAutoScriptBlocker scriptBlocker;
243 : // Need to check whether aShell has been destroyed (but not yet deleted).
244 : // In that case presContext->GetPresShell() returns nsnull.
245 : // See bug 338129.
246 0 : if (aShell && aShell->GetPresContext() &&
247 0 : aShell->GetPresContext()->GetPresShell() == aShell) {
248 0 : nsCOMPtr<nsIDocumentObserver> docObserver = do_QueryInterface(aShell);
249 0 : if (docObserver) {
250 : // Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell
251 : // knows we're messing with the frame tree.
252 0 : nsCOMPtr<nsIDOMDocument> domDocument;
253 0 : GetDocument(getter_AddRefs(domDocument));
254 0 : nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument);
255 0 : if (document)
256 0 : docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL);
257 :
258 0 : docObserver->ContentRemoved(content->GetCurrentDoc(),
259 : aParentContent, content, -1,
260 0 : content->GetPreviousSibling());
261 0 : if (document)
262 0 : docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL);
263 : }
264 : }
265 0 : content->UnbindFromTree();
266 : }
267 : }
268 0 : }
269 :
270 : // The following method is mostly called by a selection listener. When a
271 : // selection change is notified, the method is called to check if resizing
272 : // handles, a grabber and/or inline table editing UI need to be displayed
273 : // or refreshed
274 : NS_IMETHODIMP
275 0 : nsHTMLEditor::CheckSelectionStateForAnonymousButtons(nsISelection * aSelection)
276 : {
277 0 : NS_ENSURE_ARG_POINTER(aSelection);
278 :
279 : // early way out if all contextual UI extensions are disabled
280 0 : NS_ENSURE_TRUE(mIsObjectResizingEnabled ||
281 : mIsAbsolutelyPositioningEnabled ||
282 : mIsInlineTableEditingEnabled, NS_OK);
283 :
284 : // Don't change selection state if we're moving.
285 0 : if (mIsMoving) {
286 0 : return NS_OK;
287 : }
288 :
289 0 : nsCOMPtr<nsIDOMElement> focusElement;
290 : // let's get the containing element of the selection
291 0 : nsresult res = GetSelectionContainer(getter_AddRefs(focusElement));
292 0 : NS_ENSURE_TRUE(focusElement, NS_OK);
293 0 : NS_ENSURE_SUCCESS(res, res);
294 :
295 : // what's its tag?
296 0 : nsAutoString focusTagName;
297 0 : res = focusElement->GetTagName(focusTagName);
298 0 : NS_ENSURE_SUCCESS(res, res);
299 0 : ToLowerCase(focusTagName);
300 0 : nsCOMPtr<nsIAtom> focusTagAtom = do_GetAtom(focusTagName);
301 :
302 0 : nsCOMPtr<nsIDOMElement> absPosElement;
303 0 : if (mIsAbsolutelyPositioningEnabled) {
304 : // Absolute Positioning support is enabled, is the selection contained
305 : // in an absolutely positioned element ?
306 0 : res = GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(absPosElement));
307 0 : NS_ENSURE_SUCCESS(res, res);
308 : }
309 :
310 0 : nsCOMPtr<nsIDOMElement> cellElement;
311 0 : if (mIsObjectResizingEnabled || mIsInlineTableEditingEnabled) {
312 : // Resizing or Inline Table Editing is enabled, we need to check if the
313 : // selection is contained in a table cell
314 0 : res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"),
315 : nsnull,
316 0 : getter_AddRefs(cellElement));
317 0 : NS_ENSURE_SUCCESS(res, res);
318 : }
319 :
320 0 : if (mIsObjectResizingEnabled && cellElement) {
321 : // we are here because Resizing is enabled AND selection is contained in
322 : // a cell
323 :
324 : // get the enclosing table
325 0 : if (nsEditProperty::img != focusTagAtom) {
326 : // the element container of the selection is not an image, so we'll show
327 : // the resizers around the table
328 0 : nsCOMPtr<nsIDOMNode> tableNode = GetEnclosingTable(cellElement);
329 0 : focusElement = do_QueryInterface(tableNode);
330 0 : focusTagAtom = nsEditProperty::table;
331 : }
332 : }
333 :
334 : // we allow resizers only around images, tables, and absolutely positioned
335 : // elements. If we don't have image/table, let's look at the latter case.
336 0 : if (nsEditProperty::img != focusTagAtom &&
337 0 : nsEditProperty::table != focusTagAtom)
338 0 : focusElement = absPosElement;
339 :
340 : // at this point, focusElement contains the element for Resizing,
341 : // cellElement contains the element for InlineTableEditing
342 : // absPosElement contains the element for Positioning
343 :
344 : // Note: All the Hide/Show methods below may change attributes on real
345 : // content which means a DOMAttrModified handler may cause arbitrary
346 : // side effects while this code runs (bug 420439).
347 :
348 0 : if (mIsAbsolutelyPositioningEnabled && mAbsolutelyPositionedObject &&
349 0 : absPosElement != mAbsolutelyPositionedObject) {
350 0 : res = HideGrabber();
351 0 : NS_ENSURE_SUCCESS(res, res);
352 0 : NS_ASSERTION(!mAbsolutelyPositionedObject, "HideGrabber failed");
353 : }
354 :
355 0 : if (mIsObjectResizingEnabled && mResizedObject &&
356 0 : mResizedObject != focusElement) {
357 0 : res = HideResizers();
358 0 : NS_ENSURE_SUCCESS(res, res);
359 0 : NS_ASSERTION(!mResizedObject, "HideResizers failed");
360 : }
361 :
362 0 : if (mIsInlineTableEditingEnabled && mInlineEditedCell &&
363 0 : mInlineEditedCell != cellElement) {
364 0 : res = HideInlineTableEditingUI();
365 0 : NS_ENSURE_SUCCESS(res, res);
366 0 : NS_ASSERTION(!mInlineEditedCell, "HideInlineTableEditingUI failed");
367 : }
368 :
369 : // now, let's display all contextual UI for good
370 0 : nsIContent* hostContent = GetActiveEditingHost();
371 0 : nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent);
372 :
373 0 : if (mIsObjectResizingEnabled && focusElement &&
374 0 : IsModifiableNode(focusElement) && focusElement != hostNode) {
375 0 : if (nsEditProperty::img == focusTagAtom)
376 0 : mResizedObjectIsAnImage = true;
377 0 : if (mResizedObject)
378 0 : res = RefreshResizers();
379 : else
380 0 : res = ShowResizers(focusElement);
381 0 : NS_ENSURE_SUCCESS(res, res);
382 : }
383 :
384 0 : if (mIsAbsolutelyPositioningEnabled && absPosElement &&
385 0 : IsModifiableNode(absPosElement) && absPosElement != hostNode) {
386 0 : if (mAbsolutelyPositionedObject)
387 0 : res = RefreshGrabber();
388 : else
389 0 : res = ShowGrabberOnElement(absPosElement);
390 0 : NS_ENSURE_SUCCESS(res, res);
391 : }
392 :
393 0 : if (mIsInlineTableEditingEnabled && cellElement &&
394 0 : IsModifiableNode(cellElement) && cellElement != hostNode) {
395 0 : if (mInlineEditedCell)
396 0 : res = RefreshInlineTableEditingUI();
397 : else
398 0 : res = ShowInlineTableEditingUI(cellElement);
399 : }
400 :
401 0 : return res;
402 : }
403 :
404 : // Resizing and Absolute Positioning need to know everything about the
405 : // containing box of the element: position, size, margins, borders
406 : nsresult
407 0 : nsHTMLEditor::GetPositionAndDimensions(nsIDOMElement * aElement,
408 : PRInt32 & aX, PRInt32 & aY,
409 : PRInt32 & aW, PRInt32 & aH,
410 : PRInt32 & aBorderLeft,
411 : PRInt32 & aBorderTop,
412 : PRInt32 & aMarginLeft,
413 : PRInt32 & aMarginTop)
414 : {
415 0 : NS_ENSURE_ARG_POINTER(aElement);
416 :
417 : // Is the element positioned ? let's check the cheap way first...
418 0 : bool isPositioned = false;
419 0 : nsresult res = aElement->HasAttribute(NS_LITERAL_STRING("_moz_abspos"), &isPositioned);
420 0 : NS_ENSURE_SUCCESS(res, res);
421 0 : if (!isPositioned) {
422 : // hmmm... the expensive way now...
423 0 : nsAutoString positionStr;
424 : mHTMLCSSUtils->GetComputedProperty(aElement, nsEditProperty::cssPosition,
425 0 : positionStr);
426 0 : isPositioned = positionStr.EqualsLiteral("absolute");
427 : }
428 :
429 0 : if (isPositioned) {
430 : // Yes, it is absolutely positioned
431 0 : mResizedObjectIsAbsolutelyPositioned = true;
432 :
433 0 : nsCOMPtr<nsIDOMWindow> window;
434 0 : res = mHTMLCSSUtils->GetDefaultViewCSS(aElement, getter_AddRefs(window));
435 0 : NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
436 :
437 0 : nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl;
438 : // Get the all the computed css styles attached to the element node
439 0 : res = window->GetComputedStyle(aElement, EmptyString(), getter_AddRefs(cssDecl));
440 0 : NS_ENSURE_SUCCESS(res, res);
441 :
442 0 : aBorderLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-left-width"));
443 0 : aBorderTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-top-width"));
444 0 : aMarginLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-left"));
445 0 : aMarginTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-top"));
446 :
447 0 : aX = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("left")) +
448 0 : aMarginLeft + aBorderLeft;
449 0 : aY = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("top")) +
450 0 : aMarginTop + aBorderTop;
451 0 : aW = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("width"));
452 0 : aH = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("height"));
453 : }
454 : else {
455 0 : mResizedObjectIsAbsolutelyPositioned = false;
456 0 : nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aElement);
457 0 : if (!htmlElement) {
458 0 : return NS_ERROR_NULL_POINTER;
459 : }
460 0 : GetElementOrigin(aElement, aX, aY);
461 :
462 0 : res = htmlElement->GetOffsetWidth(&aW);
463 0 : NS_ENSURE_SUCCESS(res, res);
464 0 : res = htmlElement->GetOffsetHeight(&aH);
465 :
466 0 : aBorderLeft = 0;
467 0 : aBorderTop = 0;
468 0 : aMarginLeft = 0;
469 0 : aMarginTop = 0;
470 : }
471 0 : return res;
472 : }
473 :
474 : // self-explanatory
475 : void
476 0 : nsHTMLEditor::SetAnonymousElementPosition(PRInt32 aX, PRInt32 aY, nsIDOMElement *aElement)
477 : {
478 0 : mHTMLCSSUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("left"), aX);
479 0 : mHTMLCSSUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("top"), aY);
480 0 : }
|