Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(82)

Side by Side Diff: Source/devtools/front_end/elements/ElementsTreeOutline.js

Issue 705263002: DevTools: [Elements] Fix updates of the DOM tree upon inline text node removal (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Rebased Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> 3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Joseph Pecoraro 4 * Copyright (C) 2009 Joseph Pecoraro
5 * 5 *
6 * Redistribution and use in source and binary forms, with or without 6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions 7 * modification, are permitted provided that the following conditions
8 * are met: 8 * are met:
9 * 9 *
10 * 1. Redistributions of source code must retain the above copyright 10 * 1. Redistributions of source code must retain the above copyright
(...skipping 1006 matching lines...) Expand 10 before | Expand all | Expand 10 after
1017 * @param {!WebInspector.DOMNode} node 1017 * @param {!WebInspector.DOMNode} node
1018 * @param {boolean=} elementCloseTag 1018 * @param {boolean=} elementCloseTag
1019 */ 1019 */
1020 WebInspector.ElementsTreeElement = function(node, elementCloseTag) 1020 WebInspector.ElementsTreeElement = function(node, elementCloseTag)
1021 { 1021 {
1022 // The title will be updated in onattach. 1022 // The title will be updated in onattach.
1023 TreeElement.call(this, "", node); 1023 TreeElement.call(this, "", node);
1024 this._node = node; 1024 this._node = node;
1025 1025
1026 this._elementCloseTag = elementCloseTag; 1026 this._elementCloseTag = elementCloseTag;
1027 this._updateHasChildren(); 1027 this._updateChildrenDisplayMode();
1028 1028
1029 if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag) 1029 if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag)
1030 this._canAddAttributes = true; 1030 this._canAddAttributes = true;
1031 this._searchQuery = null; 1031 this._searchQuery = null;
1032 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildr enLimit; 1032 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildr enLimit;
1033 } 1033 }
1034 1034
1035 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500; 1035 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500;
1036 1036
1037 // A union of HTML4 and HTML5-Draft elements that explicitly 1037 // A union of HTML4 and HTML5-Draft elements that explicitly
1038 // or implicitly (for HTML5) forbid the closing tag. 1038 // or implicitly (for HTML5) forbid the closing tag.
1039 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [ 1039 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [
1040 "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "fram e", 1040 "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "fram e",
1041 "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source ", "track", "wbr" 1041 "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source ", "track", "wbr"
1042 ].keySet(); 1042 ].keySet();
1043 1043
1044 // These tags we do not allow editing their tag name. 1044 // These tags we do not allow editing their tag name.
1045 WebInspector.ElementsTreeElement.EditTagBlacklist = [ 1045 WebInspector.ElementsTreeElement.EditTagBlacklist = [
1046 "html", "head", "body" 1046 "html", "head", "body"
1047 ].keySet(); 1047 ].keySet();
1048 1048
1049 /** @enum {number} */
1050 WebInspector.ElementsTreeElement.ChildrenDisplayMode = {
1051 NoChildren: 0,
1052 InlineText: 1,
1053 HasChildren: 2
1054 }
1055
1049 WebInspector.ElementsTreeElement.prototype = { 1056 WebInspector.ElementsTreeElement.prototype = {
1050 /** 1057 /**
1051 * @return {!WebInspector.DOMNode} 1058 * @return {!WebInspector.DOMNode}
1052 */ 1059 */
1053 node: function() 1060 node: function()
1054 { 1061 {
1055 return this._node; 1062 return this._node;
1056 }, 1063 },
1057 1064
1058 /** 1065 /**
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
1241 if (!links) 1248 if (!links)
1242 return; 1249 return;
1243 1250
1244 for (var i = 0; i < links.length; ++i) 1251 for (var i = 0; i < links.length; ++i)
1245 links[i].preventFollowOnDoubleClick = true; 1252 links[i].preventFollowOnDoubleClick = true;
1246 }, 1253 },
1247 1254
1248 onpopulate: function() 1255 onpopulate: function()
1249 { 1256 {
1250 this.populated = true; 1257 this.populated = true;
1251 if (this.children.length || !this.hasChildren) 1258 if (this.children.length || !this._hasChildTreeElements())
1252 return; 1259 return;
1253 1260
1254 this.updateChildren(); 1261 this.updateChildren();
1255 }, 1262 },
1256 1263
1257 /** 1264 /**
1258 * @param {boolean=} fullRefresh 1265 * @param {boolean=} fullRefresh
1259 */ 1266 */
1260 updateChildren: function(fullRefresh) 1267 updateChildren: function(fullRefresh)
1261 { 1268 {
1262 if (!this.hasChildren) 1269 if (!this._hasChildTreeElements())
1263 return; 1270 return;
1264 console.assert(!this._elementCloseTag); 1271 console.assert(!this._elementCloseTag);
1265 this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh || false)); 1272 this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh || false));
1266 }, 1273 },
1267 1274
1268 /** 1275 /**
1269 * @param {!WebInspector.DOMNode} child 1276 * @param {!WebInspector.DOMNode} child
1270 * @param {number} index 1277 * @param {number} index
1271 * @param {boolean=} closingTag 1278 * @param {boolean=} closingTag
1272 * @return {!WebInspector.ElementsTreeElement} 1279 * @return {!WebInspector.ElementsTreeElement}
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
1345 elementToSelect = newElement; 1352 elementToSelect = newElement;
1346 // If a node was inserted in the middle of existing list dynamic ally we might need to increase the limit. 1353 // If a node was inserted in the middle of existing list dynamic ally we might need to increase the limit.
1347 if (this.expandedChildCount > this.expandedChildrenLimit) 1354 if (this.expandedChildCount > this.expandedChildrenLimit)
1348 this.expandedChildrenLimit++; 1355 this.expandedChildrenLimit++;
1349 } 1356 }
1350 } 1357 }
1351 1358
1352 this.updateTitle(); 1359 this.updateTitle();
1353 this._adjustCollapsedRange(); 1360 this._adjustCollapsedRange();
1354 1361
1355 if (this._node.nodeType() === Node.ELEMENT_NODE && this.hasChildren) 1362 if (this._node.nodeType() === Node.ELEMENT_NODE && this._hasChildTreeEle ments())
1356 this.insertChildElement(this._node, this.children.length, true); 1363 this.insertChildElement(this._node, this.children.length, true);
1357 1364
1358 // We want to restore the original selection and tree scroll position af ter a full refresh, if possible. 1365 // We want to restore the original selection and tree scroll position af ter a full refresh, if possible.
1359 if (fullRefresh && elementToSelect) { 1366 if (fullRefresh && elementToSelect) {
1360 elementToSelect.select(); 1367 elementToSelect.select();
1361 if (treeOutlineContainerElement && originalScrollTop <= treeOutlineC ontainerElement.scrollHeight) 1368 if (treeOutlineContainerElement && originalScrollTop <= treeOutlineC ontainerElement.scrollHeight)
1362 treeOutlineContainerElement.scrollTop = originalScrollTop; 1369 treeOutlineContainerElement.scrollTop = originalScrollTop;
1363 } 1370 }
1364 1371
1365 delete this._updateChildrenInProgress; 1372 delete this._updateChildrenInProgress;
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after
1533 * @return {boolean} 1540 * @return {boolean}
1534 */ 1541 */
1535 ondblclick: function(event) 1542 ondblclick: function(event)
1536 { 1543 {
1537 if (this._editing || this._elementCloseTag) 1544 if (this._editing || this._elementCloseTag)
1538 return false; 1545 return false;
1539 1546
1540 if (this._startEditingTarget(/** @type {!Element} */(event.target))) 1547 if (this._startEditingTarget(/** @type {!Element} */(event.target)))
1541 return false; 1548 return false;
1542 1549
1543 if (this.hasChildren && !this.expanded) 1550 if (this._hasChildTreeElements() && !this.expanded)
1544 this.expand(); 1551 this.expand();
1545 return false; 1552 return false;
1546 }, 1553 },
1547 1554
1548 /** 1555 /**
1549 * @return {boolean} 1556 * @return {boolean}
1550 */ 1557 */
1551 hasEditableNode: function() 1558 hasEditableNode: function()
1552 { 1559 {
1553 return !this._node.isShadowRoot() && !this._node.ancestorUserAgentShadow Root(); 1560 return !this._node.isShadowRoot() && !this._node.ancestorUserAgentShadow Root();
(...skipping 832 matching lines...) Expand 10 before | Expand all | Expand 10 after
2386 result += text.substring(lastIndexAfterEntity); 2393 result += text.substring(lastIndexAfterEntity);
2387 return {text: result || text, entityRanges: entityRanges}; 2394 return {text: result || text, entityRanges: entityRanges};
2388 }, 2395 },
2389 2396
2390 /** 2397 /**
2391 * @param {function(string, string, string, boolean=, string=)=} linkify 2398 * @param {function(string, string, string, boolean=, string=)=} linkify
2392 */ 2399 */
2393 _nodeTitleInfo: function(linkify) 2400 _nodeTitleInfo: function(linkify)
2394 { 2401 {
2395 var node = this._node; 2402 var node = this._node;
2396 var info = {titleDOM: createDocumentFragment(), hasChildren: this.hasChi ldren}; 2403 var info = {titleDOM: createDocumentFragment(), hasChildren: this._hasCh ildTreeElements()};
2397 2404
2398 switch (node.nodeType()) { 2405 switch (node.nodeType()) {
2399 case Node.ATTRIBUTE_NODE: 2406 case Node.ATTRIBUTE_NODE:
2400 this._buildAttributeDOM(info.titleDOM, /** @type {string} */ (no de.name), /** @type {string} */ (node.value), true); 2407 this._buildAttributeDOM(info.titleDOM, /** @type {string} */ (no de.name), /** @type {string} */ (node.value), true);
2401 break; 2408 break;
2402 2409
2403 case Node.ELEMENT_NODE: 2410 case Node.ELEMENT_NODE:
2404 var pseudoType = node.pseudoType(); 2411 var pseudoType = node.pseudoType();
2405 if (pseudoType) { 2412 if (pseudoType) {
2406 this._buildPseudoElementDOM(info.titleDOM, pseudoType); 2413 this._buildPseudoElementDOM(info.titleDOM, pseudoType);
2407 info.hasChildren = false; 2414 info.hasChildren = false;
2408 break; 2415 break;
2409 } 2416 }
2410 2417
2411 var tagName = node.nodeNameInCorrectCase(); 2418 var tagName = node.nodeNameInCorrectCase();
2412 if (this._elementCloseTag) { 2419 if (this._elementCloseTag) {
2413 this._buildTagDOM(info.titleDOM, tagName, true, true); 2420 this._buildTagDOM(info.titleDOM, tagName, true, true);
2414 info.hasChildren = false; 2421 info.hasChildren = false;
2415 break; 2422 break;
2416 } 2423 }
2417 2424
2418 this._buildTagDOM(info.titleDOM, tagName, false, false, linkify) ; 2425 this._buildTagDOM(info.titleDOM, tagName, false, false, linkify) ;
2419 2426
2420 var showInlineText = this._showInlineText() && !this.hasChildren ; 2427 switch (this._childrenDisplayMode) {
2421 if (!this.expanded && !showInlineText && (this.treeOutline.isXML MimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagNam e])) { 2428 case WebInspector.ElementsTreeElement.ChildrenDisplayMode.HasChi ldren:
2422 if (this.hasChildren) { 2429 if (!this.expanded) {
2423 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node bogus"); 2430 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node bogus");
2424 textNodeElement.textContent = "\u2026"; 2431 textNodeElement.textContent = "\u2026";
2425 info.titleDOM.createTextChild("\u200B"); 2432 info.titleDOM.createTextChild("\u200B");
2433 this._buildTagDOM(info.titleDOM, tagName, true, false);
2426 } 2434 }
2427 this._buildTagDOM(info.titleDOM, tagName, true, false); 2435 break;
2428 }
2429 2436
2430 // If this element only has a single child that is a text node, 2437 case WebInspector.ElementsTreeElement.ChildrenDisplayMode.Inline Text:
2431 // just show that text and the closing tag inline rather than
2432 // create a subtree for them
2433 if (showInlineText) {
2434 console.assert(!this.hasChildren);
2435 var textNodeElement = info.titleDOM.createChild("span", "web kit-html-text-node"); 2438 var textNodeElement = info.titleDOM.createChild("span", "web kit-html-text-node");
2436 var result = this._convertWhitespaceToEntities(node.firstChi ld.nodeValue()); 2439 var result = this._convertWhitespaceToEntities(node.firstChi ld.nodeValue());
2437 textNodeElement.textContent = result.text; 2440 textNodeElement.textContent = result.text;
2438 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value"); 2441 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
2439 info.titleDOM.createTextChild("\u200B"); 2442 info.titleDOM.createTextChild("\u200B");
2443 info.hasChildren = false;
2440 this._buildTagDOM(info.titleDOM, tagName, true, false); 2444 this._buildTagDOM(info.titleDOM, tagName, true, false);
2441 info.hasChildren = false; 2445 break;
2446
2447 case WebInspector.ElementsTreeElement.ChildrenDisplayMode.NoChil dren:
2448 if (this.treeOutline.isXMLMimeType || !WebInspector.Elements TreeElement.ForbiddenClosingTagElements[tagName])
2449 this._buildTagDOM(info.titleDOM, tagName, true, false);
2450 break;
2442 } 2451 }
2443 break; 2452 break;
2444 2453
2445 case Node.TEXT_NODE: 2454 case Node.TEXT_NODE:
2446 if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") { 2455 if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") {
2447 var newNode = info.titleDOM.createChild("span", "webkit-html -text-node webkit-html-js-node"); 2456 var newNode = info.titleDOM.createChild("span", "webkit-html -text-node webkit-html-js-node");
2448 newNode.textContent = node.nodeValue(); 2457 newNode.textContent = node.nodeValue();
2449 2458
2450 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntax Highlighter("text/javascript", true); 2459 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntax Highlighter("text/javascript", true);
2451 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode); 2460 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode);
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
2500 } 2509 }
2501 } 2510 }
2502 fragmentElement.textContent = node.nodeNameInCorrectCase().colla pseWhitespace(); 2511 fragmentElement.textContent = node.nodeNameInCorrectCase().colla pseWhitespace();
2503 break; 2512 break;
2504 default: 2513 default:
2505 info.titleDOM.createTextChild(node.nodeNameInCorrectCase().colla pseWhitespace()); 2514 info.titleDOM.createTextChild(node.nodeNameInCorrectCase().colla pseWhitespace());
2506 } 2515 }
2507 return info; 2516 return info;
2508 }, 2517 },
2509 2518
2510 /**
2511 * @return {boolean}
2512 */
2513 _showInlineText: function()
2514 {
2515 if (this._node.importedDocument() || this._node.templateContent() || thi s._visibleShadowRoots().length > 0 || this._node.hasPseudoElements())
2516 return false;
2517 if (this._node.nodeType() !== Node.ELEMENT_NODE)
2518 return false;
2519 if (!this._node.firstChild || this._node.firstChild !== this._node.lastC hild || this._node.firstChild.nodeType() !== Node.TEXT_NODE)
2520 return false;
2521 var textChild = this._node.firstChild;
2522 var maxInlineTextChildLength = 80;
2523 if (textChild.nodeValue().length < maxInlineTextChildLength)
2524 return true;
2525 return false;
2526 },
2527
2528 remove: function() 2519 remove: function()
2529 { 2520 {
2530 if (this._node.pseudoType()) 2521 if (this._node.pseudoType())
2531 return; 2522 return;
2532 var parentElement = this.parent; 2523 var parentElement = this.parent;
2533 if (!parentElement) 2524 if (!parentElement)
2534 return; 2525 return;
2535 2526
2536 var self = this; 2527 var self = this;
2537 function removeNodeCallback(error) 2528 function removeNodeCallback(error)
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
2696 var childCount = this._node.childNodeCount() + this._visibleShadowRoots( ).length; 2687 var childCount = this._node.childNodeCount() + this._visibleShadowRoots( ).length;
2697 if (this._node.importedDocument()) 2688 if (this._node.importedDocument())
2698 ++childCount; 2689 ++childCount;
2699 if (this._node.templateContent()) 2690 if (this._node.templateContent())
2700 ++childCount; 2691 ++childCount;
2701 for (var pseudoType in this._node.pseudoElements()) 2692 for (var pseudoType in this._node.pseudoElements())
2702 ++childCount; 2693 ++childCount;
2703 return childCount; 2694 return childCount;
2704 }, 2695 },
2705 2696
2706 _updateHasChildren: function() 2697 /**
2698 * @return {boolean}
2699 */
2700 _hasChildTreeElements: function()
2707 { 2701 {
2708 this.hasChildren = !this._elementCloseTag && !this._showInlineText() && this._visibleChildCount() > 0; 2702 return this._childrenDisplayMode === WebInspector.ElementsTreeElement.Ch ildrenDisplayMode.HasChildren;
2703 },
2704
2705 /**
2706 * @return {boolean}
2707 */
2708 _canShowInlineText: function()
2709 {
2710 if (this._node.importedDocument() || this._node.templateContent() || thi s._visibleShadowRoots().length > 0 || this._node.hasPseudoElements())
2711 return false;
2712 if (this._node.nodeType() !== Node.ELEMENT_NODE)
2713 return false;
2714 if (!this._node.firstChild || this._node.firstChild !== this._node.lastC hild || this._node.firstChild.nodeType() !== Node.TEXT_NODE)
2715 return false;
2716 var textChild = this._node.firstChild;
2717 var maxInlineTextChildLength = 80;
2718 if (textChild.nodeValue().length < maxInlineTextChildLength)
2719 return true;
2720 return false;
2721 },
2722
2723 _updateChildrenDisplayMode: function()
2724 {
2725 var showInlineText = this._canShowInlineText();
2726 var hasChildren = !this._elementCloseTag && this._visibleChildCount() > 0;
2727
2728 if (showInlineText)
2729 this._childrenDisplayMode = WebInspector.ElementsTreeElement.Childre nDisplayMode.InlineText;
2730 else if (hasChildren)
2731 this._childrenDisplayMode = WebInspector.ElementsTreeElement.Childre nDisplayMode.HasChildren;
2732 else
2733 this._childrenDisplayMode = WebInspector.ElementsTreeElement.Childre nDisplayMode.NoChildren;
2734
2735 this.setHasChildren(this._childrenDisplayMode === WebInspector.ElementsT reeElement.ChildrenDisplayMode.HasChildren);
2709 }, 2736 },
2710 2737
2711 __proto__: TreeElement.prototype 2738 __proto__: TreeElement.prototype
2712 } 2739 }
2713 2740
2714 /** 2741 /**
2715 * @constructor 2742 * @constructor
2716 * @param {!WebInspector.DOMModel} domModel 2743 * @param {!WebInspector.DOMModel} domModel
2717 * @param {!WebInspector.ElementsTreeOutline} treeOutline 2744 * @param {!WebInspector.ElementsTreeOutline} treeOutline
2718 */ 2745 */
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
2750 * @param {?WebInspector.DOMNode} parentNode 2777 * @param {?WebInspector.DOMNode} parentNode
2751 */ 2778 */
2752 _parentNodeModified: function(parentNode) 2779 _parentNodeModified: function(parentNode)
2753 { 2780 {
2754 if (!parentNode) 2781 if (!parentNode)
2755 return; 2782 return;
2756 this._recentlyModifiedParentNodes.add(parentNode); 2783 this._recentlyModifiedParentNodes.add(parentNode);
2757 2784
2758 var treeElement = this._treeOutline.findTreeElement(parentNode); 2785 var treeElement = this._treeOutline.findTreeElement(parentNode);
2759 if (treeElement) { 2786 if (treeElement) {
2760 var oldHasChildren = treeElement.hasChildren; 2787 var oldDisplayMode = treeElement._childrenDisplayMode;
2761 var oldShowInlineText = treeElement._showInlineText(); 2788 treeElement._updateChildrenDisplayMode();
2762 treeElement._updateHasChildren(); 2789 if (treeElement._childrenDisplayMode !== oldDisplayMode)
2763 if (treeElement.hasChildren !== oldHasChildren || oldShowInlineText || treeElement._showInlineText())
2764 this._nodeModified(parentNode); 2790 this._nodeModified(parentNode);
2765 } 2791 }
2766 2792
2767 if (this._treeOutline._visible) 2793 if (this._treeOutline._visible)
2768 this._updateModifiedNodesSoon(); 2794 this._updateModifiedNodesSoon();
2769 }, 2795 },
2770 2796
2771 /** 2797 /**
2772 * @param {!WebInspector.DOMNode} node 2798 * @param {!WebInspector.DOMNode} node
2773 */ 2799 */
(...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after
2949 treeOutline.rootDOMNode = node; 2975 treeOutline.rootDOMNode = node;
2950 if (!treeOutline.children[0].hasChildren) 2976 if (!treeOutline.children[0].hasChildren)
2951 treeOutline._element.classList.add("single-node"); 2977 treeOutline._element.classList.add("single-node");
2952 treeOutline.setVisible(true); 2978 treeOutline.setVisible(true);
2953 treeOutline.element.treeElementForTest = treeOutline.children[0] ; 2979 treeOutline.element.treeElementForTest = treeOutline.children[0] ;
2954 resolve(treeOutline.element); 2980 resolve(treeOutline.element);
2955 } 2981 }
2956 } 2982 }
2957 } 2983 }
2958 } 2984 }
OLDNEW
« no previous file with comments | « LayoutTests/inspector/elements/remove-node-expected.txt ('k') | Source/devtools/front_end/ui/treeoutline.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698