1 | /* |
2 | * Copyright (C) 2012 Google Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions are |
6 | * met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above |
11 | * copyright notice, this list of conditions and the following disclaimer |
12 | * in the documentation and/or other materials provided with the |
13 | * distribution. |
14 | * * Neither the name of Google Inc. nor the names of its |
15 | * contributors may be used to endorse or promote products derived from |
16 | * this software without specific prior written permission. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | */ |
30 | |
31 | #include "config.h" |
32 | #include "DOMEditor.h" |
33 | |
34 | #include "DOMException.h" |
35 | #include "DOMPatchSupport.h" |
36 | #include "Document.h" |
37 | #include "Element.h" |
38 | #include "InspectorHistory.h" |
39 | #include "Node.h" |
40 | #include "Text.h" |
41 | #include "markup.h" |
42 | #include <wtf/RefPtr.h> |
43 | |
44 | namespace WebCore { |
45 | |
46 | class DOMEditor::RemoveChildAction final : public InspectorHistory::Action { |
47 | WTF_MAKE_NONCOPYABLE(RemoveChildAction); |
48 | public: |
49 | RemoveChildAction(Node& parentNode, Node& node) |
50 | : InspectorHistory::Action() |
51 | , m_parentNode(parentNode) |
52 | , m_node(node) |
53 | { |
54 | } |
55 | |
56 | ExceptionOr<void> perform() final |
57 | { |
58 | m_anchorNode = m_node->nextSibling(); |
59 | return redo(); |
60 | } |
61 | |
62 | ExceptionOr<void> undo() final |
63 | { |
64 | return m_parentNode->insertBefore(m_node, m_anchorNode.get()); |
65 | } |
66 | |
67 | ExceptionOr<void> redo() final |
68 | { |
69 | return m_parentNode->removeChild(m_node); |
70 | } |
71 | |
72 | private: |
73 | Ref<Node> m_parentNode; |
74 | Ref<Node> m_node; |
75 | RefPtr<Node> m_anchorNode; |
76 | }; |
77 | |
78 | class DOMEditor::InsertBeforeAction final : public InspectorHistory::Action { |
79 | public: |
80 | InsertBeforeAction(Node& parentNode, Ref<Node>&& node, Node* anchorNode) |
81 | : InspectorHistory::Action() |
82 | , m_parentNode(parentNode) |
83 | , m_node(WTFMove(node)) |
84 | , m_anchorNode(anchorNode) |
85 | { |
86 | } |
87 | |
88 | private: |
89 | ExceptionOr<void> perform() final |
90 | { |
91 | if (m_node->parentNode()) { |
92 | m_removeChildAction = std::make_unique<RemoveChildAction>(*m_node->parentNode(), m_node); |
93 | auto result = m_removeChildAction->perform(); |
94 | if (result.hasException()) |
95 | return result.releaseException(); |
96 | } |
97 | return m_parentNode->insertBefore(m_node, m_anchorNode.get()); |
98 | } |
99 | |
100 | ExceptionOr<void> undo() final |
101 | { |
102 | auto result = m_parentNode->removeChild(m_node); |
103 | if (result.hasException()) |
104 | return result.releaseException(); |
105 | if (!m_removeChildAction) |
106 | return { }; |
107 | return m_removeChildAction->undo(); |
108 | } |
109 | |
110 | ExceptionOr<void> redo() final |
111 | { |
112 | if (m_removeChildAction) { |
113 | auto result = m_removeChildAction->redo(); |
114 | if (result.hasException()) |
115 | return result.releaseException(); |
116 | } |
117 | return m_parentNode->insertBefore(m_node, m_anchorNode.get()); |
118 | } |
119 | |
120 | Ref<Node> m_parentNode; |
121 | Ref<Node> m_node; |
122 | RefPtr<Node> m_anchorNode; |
123 | std::unique_ptr<RemoveChildAction> m_removeChildAction; |
124 | }; |
125 | |
126 | class DOMEditor::RemoveAttributeAction final : public InspectorHistory::Action { |
127 | WTF_MAKE_NONCOPYABLE(RemoveAttributeAction); |
128 | public: |
129 | RemoveAttributeAction(Element& element, const String& name) |
130 | : InspectorHistory::Action() |
131 | , m_element(element) |
132 | , m_name(name) |
133 | { |
134 | } |
135 | |
136 | private: |
137 | ExceptionOr<void> perform() final |
138 | { |
139 | m_value = m_element->getAttribute(m_name); |
140 | return redo(); |
141 | } |
142 | |
143 | ExceptionOr<void> undo() final |
144 | { |
145 | return m_element->setAttribute(m_name, m_value); |
146 | } |
147 | |
148 | ExceptionOr<void> redo() final |
149 | { |
150 | m_element->removeAttribute(m_name); |
151 | return { }; |
152 | } |
153 | |
154 | Ref<Element> m_element; |
155 | String m_name; |
156 | String m_value; |
157 | }; |
158 | |
159 | class DOMEditor::SetAttributeAction final : public InspectorHistory::Action { |
160 | WTF_MAKE_NONCOPYABLE(SetAttributeAction); |
161 | public: |
162 | SetAttributeAction(Element& element, const AtomicString& name, const AtomicString& value) |
163 | : InspectorHistory::Action() |
164 | , m_element(element) |
165 | , m_name(name) |
166 | , m_value(value) |
167 | { |
168 | } |
169 | |
170 | private: |
171 | ExceptionOr<void> perform() final |
172 | { |
173 | m_oldValue = m_element->getAttribute(m_name); |
174 | return redo(); |
175 | } |
176 | |
177 | ExceptionOr<void> undo() final |
178 | { |
179 | if (m_oldValue.isNull()) { |
180 | m_element->removeAttribute(m_name); |
181 | return { }; |
182 | } |
183 | return m_element->setAttribute(m_name, m_oldValue); |
184 | } |
185 | |
186 | ExceptionOr<void> redo() final |
187 | { |
188 | return m_element->setAttribute(m_name, m_value); |
189 | } |
190 | |
191 | Ref<Element> m_element; |
192 | AtomicString m_name; |
193 | AtomicString m_value; |
194 | AtomicString m_oldValue; |
195 | }; |
196 | |
197 | class DOMEditor::SetOuterHTMLAction final : public InspectorHistory::Action { |
198 | public: |
199 | SetOuterHTMLAction(Node& node, const String& html) |
200 | : InspectorHistory::Action() |
201 | , m_node(node) |
202 | , m_nextSibling(node.nextSibling()) |
203 | , m_html(html) |
204 | { |
205 | } |
206 | |
207 | Node* newNode() const |
208 | { |
209 | return m_newNode.get(); |
210 | } |
211 | |
212 | private: |
213 | ExceptionOr<void> perform() final |
214 | { |
215 | m_oldHTML = serializeFragment(m_node.get(), SerializedNodes::SubtreeIncludingNode); |
216 | auto result = DOMPatchSupport { m_domEditor, m_node->document() }.patchNode(m_node, m_html); |
217 | if (result.hasException()) |
218 | return result.releaseException(); |
219 | m_newNode = result.releaseReturnValue(); |
220 | return { }; |
221 | } |
222 | |
223 | ExceptionOr<void> undo() final |
224 | { |
225 | return m_history.undo(); |
226 | } |
227 | |
228 | ExceptionOr<void> redo() final |
229 | { |
230 | return m_history.redo(); |
231 | } |
232 | |
233 | Ref<Node> m_node; |
234 | RefPtr<Node> m_nextSibling; |
235 | String m_html; |
236 | String m_oldHTML; |
237 | RefPtr<Node> m_newNode { nullptr }; |
238 | InspectorHistory m_history; |
239 | DOMEditor m_domEditor { m_history }; |
240 | }; |
241 | |
242 | class DOMEditor::InsertAdjacentHTMLAction final : public InspectorHistory::Action { |
243 | WTF_MAKE_NONCOPYABLE(InsertAdjacentHTMLAction); |
244 | public: |
245 | InsertAdjacentHTMLAction(Element& element, const String& position, const String& html) |
246 | : InspectorHistory::Action() |
247 | , m_element(element) |
248 | , m_position(position) |
249 | , m_html(html) |
250 | { |
251 | } |
252 | |
253 | private: |
254 | ExceptionOr<void> perform() final |
255 | { |
256 | return redo(); |
257 | } |
258 | |
259 | ExceptionOr<void> undo() final |
260 | { |
261 | for (auto& addedNode : m_addedNodes) |
262 | addedNode->remove(); |
263 | m_addedNodes.clear(); |
264 | return { }; |
265 | } |
266 | |
267 | ExceptionOr<void> redo() final |
268 | { |
269 | auto result = m_element->insertAdjacentHTML(m_position, m_html, &m_addedNodes); |
270 | if (result.hasException()) |
271 | return result.releaseException(); |
272 | return { }; |
273 | } |
274 | |
275 | Ref<Element> m_element; |
276 | NodeVector m_addedNodes; |
277 | String m_position; |
278 | String m_html; |
279 | }; |
280 | |
281 | class DOMEditor::ReplaceWholeTextAction final : public InspectorHistory::Action { |
282 | WTF_MAKE_NONCOPYABLE(ReplaceWholeTextAction); |
283 | public: |
284 | ReplaceWholeTextAction(Text& textNode, const String& text) |
285 | : InspectorHistory::Action() |
286 | , m_textNode(textNode) |
287 | , m_text(text) |
288 | { |
289 | } |
290 | |
291 | private: |
292 | ExceptionOr<void> perform() final |
293 | { |
294 | m_oldText = m_textNode->wholeText(); |
295 | return redo(); |
296 | } |
297 | |
298 | ExceptionOr<void> undo() final |
299 | { |
300 | m_textNode->replaceWholeText(m_oldText); |
301 | return { }; |
302 | } |
303 | |
304 | ExceptionOr<void> redo() final |
305 | { |
306 | m_textNode->replaceWholeText(m_text); |
307 | return { }; |
308 | } |
309 | |
310 | Ref<Text> m_textNode; |
311 | String m_text; |
312 | String m_oldText; |
313 | }; |
314 | |
315 | class DOMEditor::ReplaceChildNodeAction final: public InspectorHistory::Action { |
316 | WTF_MAKE_NONCOPYABLE(ReplaceChildNodeAction); |
317 | public: |
318 | ReplaceChildNodeAction(Node& parentNode, Ref<Node>&& newNode, Node& oldNode) |
319 | : InspectorHistory::Action() |
320 | , m_parentNode(parentNode) |
321 | , m_newNode(WTFMove(newNode)) |
322 | , m_oldNode(oldNode) |
323 | { |
324 | } |
325 | |
326 | private: |
327 | ExceptionOr<void> perform() final |
328 | { |
329 | return redo(); |
330 | } |
331 | |
332 | ExceptionOr<void> undo() final |
333 | { |
334 | return m_parentNode->replaceChild(m_oldNode, m_newNode); |
335 | } |
336 | |
337 | ExceptionOr<void> redo() final |
338 | { |
339 | return m_parentNode->replaceChild(m_newNode, m_oldNode); |
340 | } |
341 | |
342 | Ref<Node> m_parentNode; |
343 | Ref<Node> m_newNode; |
344 | Ref<Node> m_oldNode; |
345 | }; |
346 | |
347 | class DOMEditor::SetNodeValueAction final : public InspectorHistory::Action { |
348 | WTF_MAKE_NONCOPYABLE(SetNodeValueAction); |
349 | public: |
350 | SetNodeValueAction(Node& node, const String& value) |
351 | : InspectorHistory::Action() |
352 | , m_node(node) |
353 | , m_value(value) |
354 | { |
355 | } |
356 | |
357 | private: |
358 | ExceptionOr<void> perform() final |
359 | { |
360 | m_oldValue = m_node->nodeValue(); |
361 | return redo(); |
362 | } |
363 | |
364 | ExceptionOr<void> undo() final |
365 | { |
366 | return m_node->setNodeValue(m_oldValue); |
367 | } |
368 | |
369 | ExceptionOr<void> redo() final |
370 | { |
371 | return m_node->setNodeValue(m_value); |
372 | } |
373 | |
374 | Ref<Node> m_node; |
375 | String m_value; |
376 | String m_oldValue; |
377 | }; |
378 | |
379 | DOMEditor::DOMEditor(InspectorHistory& history) |
380 | : m_history(history) |
381 | { |
382 | } |
383 | |
384 | DOMEditor::~DOMEditor() = default; |
385 | |
386 | ExceptionOr<void> DOMEditor::insertBefore(Node& parentNode, Ref<Node>&& node, Node* anchorNode) |
387 | { |
388 | return m_history.perform(std::make_unique<InsertBeforeAction>(parentNode, WTFMove(node), anchorNode)); |
389 | } |
390 | |
391 | ExceptionOr<void> DOMEditor::removeChild(Node& parentNode, Node& node) |
392 | { |
393 | return m_history.perform(std::make_unique<RemoveChildAction>(parentNode, node)); |
394 | } |
395 | |
396 | ExceptionOr<void> DOMEditor::setAttribute(Element& element, const String& name, const String& value) |
397 | { |
398 | return m_history.perform(std::make_unique<SetAttributeAction>(element, name, value)); |
399 | } |
400 | |
401 | ExceptionOr<void> DOMEditor::removeAttribute(Element& element, const String& name) |
402 | { |
403 | return m_history.perform(std::make_unique<RemoveAttributeAction>(element, name)); |
404 | } |
405 | |
406 | ExceptionOr<void> DOMEditor::setOuterHTML(Node& node, const String& html, Node*& newNode) |
407 | { |
408 | auto action = std::make_unique<SetOuterHTMLAction>(node, html); |
409 | auto& rawAction = *action; |
410 | auto result = m_history.perform(WTFMove(action)); |
411 | if (!result.hasException()) |
412 | newNode = rawAction.newNode(); |
413 | return result; |
414 | } |
415 | |
416 | ExceptionOr<void> DOMEditor::insertAdjacentHTML(Element& element, const String& where, const String& html) |
417 | { |
418 | return m_history.perform(std::make_unique<InsertAdjacentHTMLAction>(element, where, html)); |
419 | } |
420 | |
421 | ExceptionOr<void> DOMEditor::replaceWholeText(Text& textNode, const String& text) |
422 | { |
423 | return m_history.perform(std::make_unique<ReplaceWholeTextAction>(textNode, text)); |
424 | } |
425 | |
426 | ExceptionOr<void> DOMEditor::replaceChild(Node& parentNode, Ref<Node>&& newNode, Node& oldNode) |
427 | { |
428 | return m_history.perform(std::make_unique<ReplaceChildNodeAction>(parentNode, WTFMove(newNode), oldNode)); |
429 | } |
430 | |
431 | ExceptionOr<void> DOMEditor::setNodeValue(Node& node, const String& value) |
432 | { |
433 | return m_history.perform(std::make_unique<SetNodeValueAction>(node, value)); |
434 | } |
435 | |
436 | static bool populateErrorString(ExceptionOr<void>&& result, ErrorString& errorString) |
437 | { |
438 | if (!result.hasException()) |
439 | return true; |
440 | errorString = DOMException::name(result.releaseException().code()); |
441 | return false; |
442 | } |
443 | |
444 | bool DOMEditor::insertBefore(Node& parentNode, Ref<Node>&& node, Node* anchorNode, ErrorString& errorString) |
445 | { |
446 | return populateErrorString(insertBefore(parentNode, WTFMove(node), anchorNode), errorString); |
447 | } |
448 | |
449 | bool DOMEditor::removeChild(Node& parentNode, Node& node, ErrorString& errorString) |
450 | { |
451 | return populateErrorString(removeChild(parentNode, node), errorString); |
452 | } |
453 | |
454 | bool DOMEditor::setAttribute(Element& element, const String& name, const String& value, ErrorString& errorString) |
455 | { |
456 | return populateErrorString(setAttribute(element, name, value), errorString); |
457 | } |
458 | |
459 | bool DOMEditor::removeAttribute(Element& element, const String& name, ErrorString& errorString) |
460 | { |
461 | return populateErrorString(removeAttribute(element, name), errorString); |
462 | } |
463 | |
464 | bool DOMEditor::setOuterHTML(Node& node, const String& html, Node*& newNode, ErrorString& errorString) |
465 | { |
466 | return populateErrorString(setOuterHTML(node, html, newNode), errorString); |
467 | } |
468 | |
469 | bool DOMEditor::insertAdjacentHTML(Element& element, const String& where, const String& html, ErrorString& errorString) |
470 | { |
471 | return populateErrorString(insertAdjacentHTML(element, where, html), errorString); |
472 | } |
473 | |
474 | bool DOMEditor::replaceWholeText(Text& textNode, const String& text, ErrorString& errorString) |
475 | { |
476 | return populateErrorString(replaceWholeText(textNode, text), errorString); |
477 | } |
478 | |
479 | } // namespace WebCore |
480 | |