1 /**
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2000 Stefan Schimanski (1Stein@gmx.de)
5 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 #include "config.h"
24 #include "core/html/HTMLPlugInElement.h"
25
26 #include "bindings/v8/ScriptController.h"
27 #include "bindings/v8/npruntime_impl.h"
28 #include "core/CSSPropertyNames.h"
29 #include "core/HTMLNames.h"
30 #include "core/dom/Document.h"
31 #include "core/dom/Node.h"
32 #include "core/dom/shadow/ShadowRoot.h"
33 #include "core/events/Event.h"
34 #include "core/frame/FrameView.h"
35 #include "core/frame/LocalFrame.h"
36 #include "core/frame/csp/ContentSecurityPolicy.h"
37 #include "core/html/HTMLContentElement.h"
38 #include "core/html/HTMLImageLoader.h"
39 #include "core/html/PluginDocument.h"
40 #include "core/loader/FrameLoaderClient.h"
41 #include "core/page/EventHandler.h"
42 #include "core/page/Page.h"
43 #include "core/frame/Settings.h"
44 #include "core/plugins/PluginView.h"
45 #include "core/rendering/RenderEmbeddedObject.h"
46 #include "core/rendering/RenderImage.h"
47 #include "core/rendering/RenderWidget.h"
48 #include "platform/Logging.h"
49 #include "platform/MIMETypeFromURL.h"
50 #include "platform/MIMETypeRegistry.h"
51 #include "platform/Widget.h"
52 #include "platform/plugins/PluginData.h"
53
54 namespace WebCore {
55
56 using namespace HTMLNames;
57
HTMLPlugInElement(const QualifiedName & tagName,Document & doc,bool createdByParser,PreferPlugInsForImagesOption preferPlugInsForImagesOption)58 HTMLPlugInElement::HTMLPlugInElement(const QualifiedName& tagName, Document& doc, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption)
59 : HTMLFrameOwnerElement(tagName, doc)
60 , m_isDelayingLoadEvent(false)
61 , m_NPObject(0)
62 , m_isCapturingMouseEvents(false)
63 // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay
64 // widget updates until after all children are parsed. For HTMLEmbedElement
65 // this delay is unnecessary, but it is simpler to make both classes share
66 // the same codepath in this class.
67 , m_needsWidgetUpdate(!createdByParser)
68 , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages)
69 , m_displayState(Playing)
70 {
71 setHasCustomStyleCallbacks();
72 }
73
~HTMLPlugInElement()74 HTMLPlugInElement::~HTMLPlugInElement()
75 {
76 ASSERT(!m_pluginWrapper); // cleared in detach()
77 ASSERT(!m_isDelayingLoadEvent);
78
79 if (m_NPObject) {
80 _NPN_ReleaseObject(m_NPObject);
81 m_NPObject = 0;
82 }
83 }
84
trace(Visitor * visitor)85 void HTMLPlugInElement::trace(Visitor* visitor)
86 {
87 visitor->trace(m_imageLoader);
88 HTMLFrameOwnerElement::trace(visitor);
89 }
90
canProcessDrag() const91 bool HTMLPlugInElement::canProcessDrag() const
92 {
93 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag();
94 }
95
willRespondToMouseClickEvents()96 bool HTMLPlugInElement::willRespondToMouseClickEvents()
97 {
98 if (isDisabledFormControl())
99 return false;
100 RenderObject* r = renderer();
101 return r && (r->isEmbeddedObject() || r->isWidget());
102 }
103
removeAllEventListeners()104 void HTMLPlugInElement::removeAllEventListeners()
105 {
106 HTMLFrameOwnerElement::removeAllEventListeners();
107 if (RenderWidget* renderer = existingRenderWidget()) {
108 if (Widget* widget = renderer->widget())
109 widget->eventListenersRemoved();
110 }
111 }
112
didMoveToNewDocument(Document & oldDocument)113 void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument)
114 {
115 if (m_imageLoader)
116 m_imageLoader->elementDidMoveToNewDocument();
117 HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
118 }
119
attach(const AttachContext & context)120 void HTMLPlugInElement::attach(const AttachContext& context)
121 {
122 HTMLFrameOwnerElement::attach(context);
123
124 if (!renderer() || useFallbackContent())
125 return;
126
127 if (isImageType()) {
128 if (!m_imageLoader)
129 m_imageLoader = HTMLImageLoader::create(this);
130 m_imageLoader->updateFromElement();
131 } else if (needsWidgetUpdate()
132 && renderEmbeddedObject()
133 && !renderEmbeddedObject()->showsUnavailablePluginIndicator()
134 && !wouldLoadAsNetscapePlugin(m_url, m_serviceType)
135 && !m_isDelayingLoadEvent) {
136 m_isDelayingLoadEvent = true;
137 document().incrementLoadEventDelayCount();
138 document().loadPluginsSoon();
139 }
140 }
141
updateWidget()142 void HTMLPlugInElement::updateWidget()
143 {
144 RefPtrWillBeRawPtr<HTMLPlugInElement> protector(this);
145 updateWidgetInternal();
146 if (m_isDelayingLoadEvent) {
147 m_isDelayingLoadEvent = false;
148 document().decrementLoadEventDelayCount();
149 }
150 }
151
requestPluginCreationWithoutRendererIfPossible()152 void HTMLPlugInElement::requestPluginCreationWithoutRendererIfPossible()
153 {
154 if (m_serviceType.isEmpty())
155 return;
156
157 if (!document().frame()
158 || !document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType))
159 return;
160
161 if (renderer() && renderer()->isWidget())
162 return;
163
164 createPluginWithoutRenderer();
165 }
166
createPluginWithoutRenderer()167 void HTMLPlugInElement::createPluginWithoutRenderer()
168 {
169 ASSERT(document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType));
170
171 KURL url;
172 Vector<String> paramNames;
173 Vector<String> paramValues;
174
175 paramNames.append("type");
176 paramValues.append(m_serviceType);
177
178 bool useFallback = false;
179 loadPlugin(url, m_serviceType, paramNames, paramValues, useFallback, false);
180 }
181
shouldAccelerate() const182 bool HTMLPlugInElement::shouldAccelerate() const
183 {
184 if (Widget* widget = ownedWidget())
185 return widget->isPluginView() && toPluginView(widget)->platformLayer();
186 return false;
187 }
188
detach(const AttachContext & context)189 void HTMLPlugInElement::detach(const AttachContext& context)
190 {
191 // Update the widget the next time we attach (detaching destroys the plugin).
192 // FIXME: None of this "needsWidgetUpdate" related code looks right.
193 if (renderer() && !useFallbackContent())
194 setNeedsWidgetUpdate(true);
195 if (m_isDelayingLoadEvent) {
196 m_isDelayingLoadEvent = false;
197 document().decrementLoadEventDelayCount();
198 }
199
200 // Only try to persist a plugin widget we actually own.
201 Widget* plugin = ownedWidget();
202 if (plugin && plugin->pluginShouldPersist())
203 m_persistedPluginWidget = plugin;
204 resetInstance();
205 // FIXME - is this next line necessary?
206 setWidget(nullptr);
207
208 if (m_isCapturingMouseEvents) {
209 if (LocalFrame* frame = document().frame())
210 frame->eventHandler().setCapturingMouseEventsNode(nullptr);
211 m_isCapturingMouseEvents = false;
212 }
213
214 if (m_NPObject) {
215 _NPN_ReleaseObject(m_NPObject);
216 m_NPObject = 0;
217 }
218
219 HTMLFrameOwnerElement::detach(context);
220 }
221
createRenderer(RenderStyle * style)222 RenderObject* HTMLPlugInElement::createRenderer(RenderStyle* style)
223 {
224 // Fallback content breaks the DOM->Renderer class relationship of this
225 // class and all superclasses because createObject won't necessarily
226 // return a RenderEmbeddedObject, RenderPart or even RenderWidget.
227 if (useFallbackContent())
228 return RenderObject::createObject(this, style);
229
230 if (isImageType()) {
231 RenderImage* image = new RenderImage(this);
232 image->setImageResource(RenderImageResource::create());
233 return image;
234 }
235
236 return new RenderEmbeddedObject(this);
237 }
238
willRecalcStyle(StyleRecalcChange)239 void HTMLPlugInElement::willRecalcStyle(StyleRecalcChange)
240 {
241 // FIXME: Why is this necessary? Manual re-attach is almost always wrong.
242 if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType())
243 reattach();
244 }
245
finishParsingChildren()246 void HTMLPlugInElement::finishParsingChildren()
247 {
248 HTMLFrameOwnerElement::finishParsingChildren();
249 if (useFallbackContent())
250 return;
251
252 setNeedsWidgetUpdate(true);
253 if (inDocument())
254 setNeedsStyleRecalc(SubtreeStyleChange);
255 }
256
resetInstance()257 void HTMLPlugInElement::resetInstance()
258 {
259 m_pluginWrapper.clear();
260 }
261
pluginWrapper()262 SharedPersistent<v8::Object>* HTMLPlugInElement::pluginWrapper()
263 {
264 LocalFrame* frame = document().frame();
265 if (!frame)
266 return 0;
267
268 // If the host dynamically turns off JavaScript (or Java) we will still
269 // return the cached allocated Bindings::Instance. Not supporting this
270 // edge-case is OK.
271 if (!m_pluginWrapper) {
272 Widget* plugin;
273
274 if (m_persistedPluginWidget)
275 plugin = m_persistedPluginWidget.get();
276 else
277 plugin = pluginWidget();
278
279 if (plugin)
280 m_pluginWrapper = frame->script().createPluginWrapper(plugin);
281 }
282 return m_pluginWrapper.get();
283 }
284
pluginWidget() const285 Widget* HTMLPlugInElement::pluginWidget() const
286 {
287 if (RenderWidget* renderWidget = renderWidgetForJSBindings())
288 return renderWidget->widget();
289 return 0;
290 }
291
isPresentationAttribute(const QualifiedName & name) const292 bool HTMLPlugInElement::isPresentationAttribute(const QualifiedName& name) const
293 {
294 if (name == widthAttr || name == heightAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr)
295 return true;
296 return HTMLFrameOwnerElement::isPresentationAttribute(name);
297 }
298
collectStyleForPresentationAttribute(const QualifiedName & name,const AtomicString & value,MutableStylePropertySet * style)299 void HTMLPlugInElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
300 {
301 if (name == widthAttr) {
302 addHTMLLengthToStyle(style, CSSPropertyWidth, value);
303 } else if (name == heightAttr) {
304 addHTMLLengthToStyle(style, CSSPropertyHeight, value);
305 } else if (name == vspaceAttr) {
306 addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
307 addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
308 } else if (name == hspaceAttr) {
309 addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
310 addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
311 } else if (name == alignAttr) {
312 applyAlignmentAttributeToStyle(value, style);
313 } else {
314 HTMLFrameOwnerElement::collectStyleForPresentationAttribute(name, value, style);
315 }
316 }
317
defaultEventHandler(Event * event)318 void HTMLPlugInElement::defaultEventHandler(Event* event)
319 {
320 // Firefox seems to use a fake event listener to dispatch events to plug-in
321 // (tested with mouse events only). This is observable via different order
322 // of events - in Firefox, event listeners specified in HTML attributes
323 // fires first, then an event gets dispatched to plug-in, and only then
324 // other event listeners fire. Hopefully, this difference does not matter in
325 // practice.
326
327 // FIXME: Mouse down and scroll events are passed down to plug-in via custom
328 // code in EventHandler; these code paths should be united.
329
330 RenderObject* r = renderer();
331 if (!r || !r->isWidget())
332 return;
333 if (r->isEmbeddedObject()) {
334 if (toRenderEmbeddedObject(r)->showsUnavailablePluginIndicator())
335 return;
336 if (displayState() < Playing)
337 return;
338 }
339 RefPtr<Widget> widget = toRenderWidget(r)->widget();
340 if (!widget)
341 return;
342 widget->handleEvent(event);
343 if (event->defaultHandled())
344 return;
345 HTMLFrameOwnerElement::defaultEventHandler(event);
346 }
347
renderWidgetForJSBindings() const348 RenderWidget* HTMLPlugInElement::renderWidgetForJSBindings() const
349 {
350 // Needs to load the plugin immediatedly because this function is called
351 // when JavaScript code accesses the plugin.
352 // FIXME: Check if dispatching events here is safe.
353 document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasksSynchronously);
354 return existingRenderWidget();
355 }
356
isKeyboardFocusable() const357 bool HTMLPlugInElement::isKeyboardFocusable() const
358 {
359 if (!document().isActive())
360 return false;
361 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus();
362 }
363
hasCustomFocusLogic() const364 bool HTMLPlugInElement::hasCustomFocusLogic() const
365 {
366 return !hasAuthorShadowRoot();
367 }
368
isPluginElement() const369 bool HTMLPlugInElement::isPluginElement() const
370 {
371 return true;
372 }
373
rendererIsFocusable() const374 bool HTMLPlugInElement::rendererIsFocusable() const
375 {
376 if (HTMLFrameOwnerElement::supportsFocus() && HTMLFrameOwnerElement::rendererIsFocusable())
377 return true;
378
379 if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject())
380 return false;
381 return !toRenderEmbeddedObject(renderer())->showsUnavailablePluginIndicator();
382 }
383
getNPObject()384 NPObject* HTMLPlugInElement::getNPObject()
385 {
386 ASSERT(document().frame());
387 if (!m_NPObject)
388 m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this);
389 return m_NPObject;
390 }
391
isImageType()392 bool HTMLPlugInElement::isImageType()
393 {
394 if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
395 m_serviceType = mimeTypeFromDataURL(m_url);
396
397 if (LocalFrame* frame = document().frame()) {
398 KURL completedURL = document().completeURL(m_url);
399 return frame->loader().client()->objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
400 }
401
402 return Image::supportsType(m_serviceType);
403 }
404
renderEmbeddedObject() const405 RenderEmbeddedObject* HTMLPlugInElement::renderEmbeddedObject() const
406 {
407 // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
408 // when using fallback content.
409 if (!renderer() || !renderer()->isEmbeddedObject())
410 return 0;
411 return toRenderEmbeddedObject(renderer());
412 }
413
414 // We don't use m_url, as it may not be the final URL that the object loads,
415 // depending on <param> values.
allowedToLoadFrameURL(const String & url)416 bool HTMLPlugInElement::allowedToLoadFrameURL(const String& url)
417 {
418 KURL completeURL = document().completeURL(url);
419 if (contentFrame() && protocolIsJavaScript(completeURL)
420 && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin()))
421 return false;
422 return document().frame()->isURLAllowed(completeURL);
423 }
424
425 // We don't use m_url, or m_serviceType as they may not be the final values
426 // that <object> uses depending on <param> values.
wouldLoadAsNetscapePlugin(const String & url,const String & serviceType)427 bool HTMLPlugInElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
428 {
429 ASSERT(document().frame());
430 KURL completedURL;
431 if (!url.isEmpty())
432 completedURL = document().completeURL(url);
433 return document().frame()->loader().client()->objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin;
434 }
435
requestObject(const String & url,const String & mimeType,const Vector<String> & paramNames,const Vector<String> & paramValues)436 bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
437 {
438 if (url.isEmpty() && mimeType.isEmpty())
439 return false;
440
441 // FIXME: None of this code should use renderers!
442 RenderEmbeddedObject* renderer = renderEmbeddedObject();
443 ASSERT(renderer);
444 if (!renderer)
445 return false;
446
447 KURL completedURL = document().completeURL(url);
448 if (!pluginIsLoadable(completedURL, mimeType))
449 return false;
450
451 bool useFallback;
452 bool requireRenderer = true;
453 if (shouldUsePlugin(completedURL, mimeType, hasFallbackContent(), useFallback))
454 return loadPlugin(completedURL, mimeType, paramNames, paramValues, useFallback, requireRenderer);
455
456 // If the plug-in element already contains a subframe,
457 // loadOrRedirectSubframe will re-use it. Otherwise, it will create a new
458 // frame and set it as the RenderPart's widget, causing what was previously
459 // in the widget to be torn down.
460 return loadOrRedirectSubframe(completedURL, getNameAttribute(), true);
461 }
462
loadPlugin(const KURL & url,const String & mimeType,const Vector<String> & paramNames,const Vector<String> & paramValues,bool useFallback,bool requireRenderer)463 bool HTMLPlugInElement::loadPlugin(const KURL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback, bool requireRenderer)
464 {
465 LocalFrame* frame = document().frame();
466
467 if (!frame->loader().allowPlugins(AboutToInstantiatePlugin))
468 return false;
469
470 RenderEmbeddedObject* renderer = renderEmbeddedObject();
471 // FIXME: This code should not depend on renderer!
472 if ((!renderer && requireRenderer) || useFallback)
473 return false;
474
475 WTF_LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
476 WTF_LOG(Plugins, " Loaded URL: %s", url.string().utf8().data());
477 m_loadedUrl = url;
478
479 RefPtr<Widget> widget = m_persistedPluginWidget;
480 if (!widget) {
481 bool loadManually = document().isPluginDocument() && !document().containsPlugins() && toPluginDocument(document()).shouldLoadPluginManually();
482 FrameLoaderClient::DetachedPluginPolicy policy = requireRenderer ? FrameLoaderClient::FailOnDetachedPlugin : FrameLoaderClient::AllowDetachedPlugin;
483 widget = frame->loader().client()->createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, policy);
484 }
485
486 if (!widget) {
487 if (renderer && !renderer->showsUnavailablePluginIndicator())
488 renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
489 return false;
490 }
491
492 if (renderer) {
493 setWidget(widget);
494 m_persistedPluginWidget = nullptr;
495 } else if (widget != m_persistedPluginWidget) {
496 m_persistedPluginWidget = widget;
497 }
498 document().setContainsPlugins();
499 scheduleSVGFilterLayerUpdateHack();
500 return true;
501 }
502
shouldUsePlugin(const KURL & url,const String & mimeType,bool hasFallback,bool & useFallback)503 bool HTMLPlugInElement::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback)
504 {
505 // Allow other plug-ins to win over QuickTime because if the user has
506 // installed a plug-in that can handle TIFF (which QuickTime can also
507 // handle) they probably intended to override QT.
508 if (document().frame()->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) {
509 const PluginData* pluginData = document().frame()->page()->pluginData();
510 String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String();
511 if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false))
512 return true;
513 }
514
515 ObjectContentType objectType = document().frame()->loader().client()->objectContentType(url, mimeType, shouldPreferPlugInsForImages());
516 // If an object's content can't be handled and it has no fallback, let
517 // it be handled as a plugin to show the broken plugin icon.
518 useFallback = objectType == ObjectContentNone && hasFallback;
519 return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin;
520
521 }
522
dispatchErrorEvent()523 void HTMLPlugInElement::dispatchErrorEvent()
524 {
525 if (document().isPluginDocument() && document().ownerElement())
526 document().ownerElement()->dispatchEvent(Event::create(EventTypeNames::error));
527 else
528 dispatchEvent(Event::create(EventTypeNames::error));
529 }
530
pluginIsLoadable(const KURL & url,const String & mimeType)531 bool HTMLPlugInElement::pluginIsLoadable(const KURL& url, const String& mimeType)
532 {
533 LocalFrame* frame = document().frame();
534 Settings* settings = frame->settings();
535 if (!settings)
536 return false;
537
538 if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType) && !settings->javaEnabled())
539 return false;
540
541 if (document().isSandboxed(SandboxPlugins))
542 return false;
543
544 if (!document().securityOrigin()->canDisplay(url)) {
545 FrameLoader::reportLocalLoadFailed(frame, url.string());
546 return false;
547 }
548
549 AtomicString declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
550 document().ownerElement()->fastGetAttribute(HTMLNames::typeAttr) :
551 fastGetAttribute(HTMLNames::typeAttr);
552 if (!document().contentSecurityPolicy()->allowObjectFromSource(url)
553 || !document().contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) {
554 renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
555 return false;
556 }
557
558 return frame->loader().mixedContentChecker()->canRunInsecureContent(document().securityOrigin(), url);
559 }
560
didAddUserAgentShadowRoot(ShadowRoot &)561 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&)
562 {
563 userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
564 }
565
willAddFirstAuthorShadowRoot()566 void HTMLPlugInElement::willAddFirstAuthorShadowRoot()
567 {
568 lazyReattachIfAttached();
569 }
570
hasFallbackContent() const571 bool HTMLPlugInElement::hasFallbackContent() const
572 {
573 return false;
574 }
575
useFallbackContent() const576 bool HTMLPlugInElement::useFallbackContent() const
577 {
578 return hasAuthorShadowRoot();
579 }
580
581 }
582