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

Side by Side Diff: content/browser/tab_contents/web_contents_view_mac.mm

Issue 10024066: TabContents -> WebContentsImpl, part 4. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 8 years, 8 months 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
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import <Carbon/Carbon.h>
6
7 #import "content/browser/tab_contents/web_contents_view_mac.h"
8
9 #include <string>
10
11 #import "base/mac/scoped_sending_event.h"
12 #import "base/message_pump_mac.h"
13 #include "content/browser/renderer_host/render_view_host_factory.h"
14 #include "content/browser/renderer_host/render_view_host_impl.h"
15 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
16 #include "content/browser/tab_contents/popup_menu_helper_mac.h"
17 #include "content/browser/tab_contents/tab_contents.h"
18 #import "content/browser/web_contents/web_drag_dest_mac.h"
19 #import "content/browser/web_contents/web_drag_source_mac.h"
20 #include "content/common/view_messages.h"
21 #include "content/public/browser/web_contents_delegate.h"
22 #include "content/public/browser/web_contents_view_delegate.h"
23 #include "skia/ext/skia_utils_mac.h"
24 #import "third_party/mozilla/NSPasteboard+Utils.h"
25 #import "ui/base/cocoa/focus_tracker.h"
26
27 using WebKit::WebDragOperation;
28 using WebKit::WebDragOperationsMask;
29 using content::RenderWidgetHostView;
30 using content::WebContents;
31
32 // Ensure that the WebKit::WebDragOperation enum values stay in sync with
33 // NSDragOperation constants, since the code below static_casts between 'em.
34 #define COMPILE_ASSERT_MATCHING_ENUM(name) \
35 COMPILE_ASSERT(int(NS##name) == int(WebKit::Web##name), enum_mismatch_##name)
36 COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone);
37 COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy);
38 COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink);
39 COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric);
40 COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate);
41 COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove);
42 COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete);
43 COMPILE_ASSERT_MATCHING_ENUM(DragOperationEvery);
44
45 @interface WebContentsViewCocoa (Private)
46 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w;
47 - (void)registerDragTypes;
48 - (void)setCurrentDragOperation:(NSDragOperation)operation;
49 - (void)startDragWithDropData:(const WebDropData&)dropData
50 dragOperationMask:(NSDragOperation)operationMask
51 image:(NSImage*)image
52 offset:(NSPoint)offset;
53 - (void)cancelDeferredClose;
54 - (void)clearTabContentsView;
55 - (void)closeTabAfterEvent;
56 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification;
57 @end
58
59 namespace web_contents_view_mac {
60 content::WebContentsView* CreateWebContentsView(
61 TabContents* tab_contents,
62 content::WebContentsViewDelegate* delegate) {
63 return new WebContentsViewMac(tab_contents, delegate);
64 }
65 }
66
67 WebContentsViewMac::WebContentsViewMac(
68 WebContentsImpl* web_contents,
69 content::WebContentsViewDelegate* delegate)
70 : web_contents_(web_contents),
71 delegate_(delegate) {
72 }
73
74 WebContentsViewMac::~WebContentsViewMac() {
75 // This handles the case where a renderer close call was deferred
76 // while the user was operating a UI control which resulted in a
77 // close. In that case, the Cocoa view outlives the
78 // WebContentsViewMac instance due to Cocoa retain count.
79 [cocoa_view_ cancelDeferredClose];
80 [cocoa_view_ clearTabContentsView];
81 }
82
83 void WebContentsViewMac::CreateView(const gfx::Size& initial_size) {
84 WebContentsViewCocoa* view =
85 [[WebContentsViewCocoa alloc] initWithWebContentsViewMac:this];
86 cocoa_view_.reset(view);
87 }
88
89 RenderWidgetHostView* WebContentsViewMac::CreateViewForWidget(
90 content::RenderWidgetHost* render_widget_host) {
91 if (render_widget_host->GetView()) {
92 // During testing, the view will already be set up in most cases to the
93 // test view, so we don't want to clobber it with a real one. To verify that
94 // this actually is happening (and somebody isn't accidentally creating the
95 // view twice), we check for the RVH Factory, which will be set when we're
96 // making special ones (which go along with the special views).
97 DCHECK(RenderViewHostFactory::has_factory());
98 return render_widget_host->GetView();
99 }
100
101 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
102 RenderWidgetHostView::CreateViewForWidget(render_widget_host));
103 if (delegate()) {
104 NSObject<RenderWidgetHostViewMacDelegate>* rw_delegate =
105 delegate()->CreateRenderWidgetHostViewDelegate(render_widget_host);
106 view->SetDelegate(rw_delegate);
107 }
108
109 // Fancy layout comes later; for now just make it our size and resize it
110 // with us. In case there are other siblings of the content area, we want
111 // to make sure the content area is on the bottom so other things draw over
112 // it.
113 NSView* view_view = view->GetNativeView();
114 [view_view setFrame:[cocoa_view_.get() bounds]];
115 [view_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
116 // Add the new view below all other views; this also keeps it below any
117 // overlay view installed.
118 [cocoa_view_.get() addSubview:view_view
119 positioned:NSWindowBelow
120 relativeTo:nil];
121 // For some reason known only to Cocoa, the autorecalculation of the key view
122 // loop set on the window doesn't set the next key view when the subview is
123 // added. On 10.6 things magically work fine; on 10.5 they fail
124 // <http://crbug.com/61493>. Digging into Cocoa key view loop code yielded
125 // madness; TODO(avi,rohit): look at this again and figure out what's really
126 // going on.
127 [cocoa_view_.get() setNextKeyView:view_view];
128 return view;
129 }
130
131 gfx::NativeView WebContentsViewMac::GetNativeView() const {
132 return cocoa_view_.get();
133 }
134
135 gfx::NativeView WebContentsViewMac::GetContentNativeView() const {
136 RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
137 if (!rwhv)
138 return NULL;
139 return rwhv->GetNativeView();
140 }
141
142 gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const {
143 return [cocoa_view_.get() window];
144 }
145
146 void WebContentsViewMac::GetContainerBounds(gfx::Rect* out) const {
147 // Convert bounds to window coordinate space.
148 NSRect bounds =
149 [cocoa_view_.get() convertRect:[cocoa_view_.get() bounds] toView:nil];
150
151 // Convert bounds to screen coordinate space.
152 NSWindow* window = [cocoa_view_.get() window];
153 bounds.origin = [window convertBaseToScreen:bounds.origin];
154
155 // Flip y to account for screen flip.
156 NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
157 bounds.origin.y = [screen frame].size.height - bounds.origin.y
158 - bounds.size.height;
159 *out = gfx::Rect(NSRectToCGRect(bounds));
160 }
161
162 void WebContentsViewMac::StartDragging(
163 const WebDropData& drop_data,
164 WebDragOperationsMask allowed_operations,
165 const SkBitmap& image,
166 const gfx::Point& image_offset) {
167 // By allowing nested tasks, the code below also allows Close(),
168 // which would deallocate |this|. The same problem can occur while
169 // processing -sendEvent:, so Close() is deferred in that case.
170 // Drags from web content do not come via -sendEvent:, this sets the
171 // same flag -sendEvent: would.
172 base::mac::ScopedSendingEvent sending_event_scoper;
173
174 // The drag invokes a nested event loop, arrange to continue
175 // processing events.
176 MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
177 NSDragOperation mask = static_cast<NSDragOperation>(allowed_operations);
178 NSPoint offset = NSPointFromCGPoint(image_offset.ToCGPoint());
179 [cocoa_view_ startDragWithDropData:drop_data
180 dragOperationMask:mask
181 image:gfx::SkBitmapToNSImage(image)
182 offset:offset];
183 }
184
185 void WebContentsViewMac::RenderViewCreated(content::RenderViewHost* host) {
186 // We want updates whenever the intrinsic width of the webpage changes.
187 // Put the RenderView into that mode. The preferred width is used for example
188 // when the "zoom" button in the browser window is clicked.
189 host->EnablePreferredSizeMode();
190 }
191
192 void WebContentsViewMac::SetPageTitle(const string16& title) {
193 // Meaningless on the Mac; widgets don't have a "title" attribute
194 }
195
196 void WebContentsViewMac::OnTabCrashed(base::TerminationStatus /* status */,
197 int /* error_code */) {
198 }
199
200 void WebContentsViewMac::SizeContents(const gfx::Size& size) {
201 // TODO(brettw | japhet) This is a hack and should be removed.
202 // See web_contents_view.h.
203 gfx::Rect rect(gfx::Point(), size);
204 WebContentsViewCocoa* view = cocoa_view_.get();
205 [view setFrame:[view flipRectToNSRect:rect]];
206 }
207
208 void WebContentsViewMac::Focus() {
209 [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
210 [[cocoa_view_.get() window] makeKeyAndOrderFront:GetContentNativeView()];
211 }
212
213 void WebContentsViewMac::SetInitialFocus() {
214 if (web_contents_->FocusLocationBarByDefault())
215 web_contents_->SetFocusToLocationBar(false);
216 else
217 [[cocoa_view_.get() window] makeFirstResponder:GetContentNativeView()];
218 }
219
220 void WebContentsViewMac::StoreFocus() {
221 // We're explicitly being asked to store focus, so don't worry if there's
222 // already a view saved.
223 focus_tracker_.reset(
224 [[FocusTracker alloc] initWithWindow:[cocoa_view_ window]]);
225 }
226
227 void WebContentsViewMac::RestoreFocus() {
228 // TODO(avi): Could we be restoring a view that's no longer in the key view
229 // chain?
230 if (!(focus_tracker_.get() &&
231 [focus_tracker_ restoreFocusInWindow:[cocoa_view_ window]])) {
232 // Fall back to the default focus behavior if we could not restore focus.
233 // TODO(shess): If location-bar gets focus by default, this will
234 // select-all in the field. If there was a specific selection in
235 // the field when we navigated away from it, we should restore
236 // that selection.
237 SetInitialFocus();
238 }
239
240 focus_tracker_.reset(nil);
241 }
242
243 bool WebContentsViewMac::IsDoingDrag() const {
244 return false;
245 }
246
247 void WebContentsViewMac::CancelDragAndCloseTab() {
248 }
249
250 void WebContentsViewMac::UpdateDragCursor(WebDragOperation operation) {
251 [cocoa_view_ setCurrentDragOperation: operation];
252 }
253
254 void WebContentsViewMac::GotFocus() {
255 // This is only used in the views FocusManager stuff but it bleeds through
256 // all subclasses. http://crbug.com/21875
257 }
258
259 // This is called when the renderer asks us to take focus back (i.e., it has
260 // iterated past the last focusable element on the page).
261 void WebContentsViewMac::TakeFocus(bool reverse) {
262 if (reverse) {
263 [[cocoa_view_ window] selectPreviousKeyView:cocoa_view_.get()];
264 } else {
265 [[cocoa_view_ window] selectNextKeyView:cocoa_view_.get()];
266 }
267 }
268
269 void WebContentsViewMac::CreateNewWindow(
270 int route_id,
271 const ViewHostMsg_CreateWindow_Params& params) {
272 tab_contents_view_helper_.CreateNewWindow(web_contents_, route_id, params);
273 }
274
275 void WebContentsViewMac::CreateNewWidget(
276 int route_id, WebKit::WebPopupType popup_type) {
277 RenderWidgetHostView* widget_view =
278 tab_contents_view_helper_.CreateNewWidget(web_contents_,
279 route_id,
280 false,
281 popup_type);
282
283 // A RenderWidgetHostViewMac has lifetime scoped to the view. We'll retain it
284 // to allow it to survive the trip without being hosted.
285 RenderWidgetHostViewMac* widget_view_mac =
286 static_cast<RenderWidgetHostViewMac*>(widget_view);
287 [widget_view_mac->GetNativeView() retain];
288 }
289
290 void WebContentsViewMac::CreateNewFullscreenWidget(int route_id) {
291 RenderWidgetHostView* widget_view =
292 tab_contents_view_helper_.CreateNewWidget(web_contents_,
293 route_id,
294 true,
295 WebKit::WebPopupTypeNone);
296
297 // A RenderWidgetHostViewMac has lifetime scoped to the view. We'll retain it
298 // to allow it to survive the trip without being hosted.
299 RenderWidgetHostViewMac* widget_view_mac =
300 static_cast<RenderWidgetHostViewMac*>(widget_view);
301 [widget_view_mac->GetNativeView() retain];
302 }
303
304 void WebContentsViewMac::ShowCreatedWindow(int route_id,
305 WindowOpenDisposition disposition,
306 const gfx::Rect& initial_pos,
307 bool user_gesture) {
308 tab_contents_view_helper_.ShowCreatedWindow(
309 web_contents_, route_id, disposition, initial_pos, user_gesture);
310 }
311
312 void WebContentsViewMac::ShowCreatedWidget(
313 int route_id, const gfx::Rect& initial_pos) {
314 RenderWidgetHostView* widget_host_view =
315 tab_contents_view_helper_.ShowCreatedWidget(web_contents_,
316 route_id,
317 false,
318 initial_pos);
319
320 // A RenderWidgetHostViewMac has lifetime scoped to the view. Now that it's
321 // properly embedded (or purposefully ignored) we can release the retain we
322 // took in CreateNewWidgetInternal().
323 RenderWidgetHostViewMac* widget_view_mac =
324 static_cast<RenderWidgetHostViewMac*>(widget_host_view);
325 [widget_view_mac->GetNativeView() release];
326 }
327
328 void WebContentsViewMac::ShowCreatedFullscreenWidget(int route_id) {
329 RenderWidgetHostView* widget_host_view =
330 tab_contents_view_helper_.ShowCreatedWidget(web_contents_,
331 route_id,
332 true,
333 gfx::Rect());
334
335 // A RenderWidgetHostViewMac has lifetime scoped to the view. Now that it's
336 // properly embedded (or purposely ignored) we can release the retain we took
337 // in CreateNewFullscreenWidgetInternal().
338 RenderWidgetHostViewMac* widget_view_mac =
339 static_cast<RenderWidgetHostViewMac*>(widget_host_view);
340 [widget_view_mac->GetNativeView() release];
341 }
342
343 void WebContentsViewMac::ShowContextMenu(
344 const content::ContextMenuParams& params) {
345 // Allow delegates to handle the context menu operation first.
346 if (web_contents_->GetDelegate() &&
347 web_contents_->GetDelegate()->HandleContextMenu(params)) {
348 return;
349 }
350
351 if (delegate())
352 delegate()->ShowContextMenu(params);
353 else
354 DLOG(ERROR) << "Cannot show context menus without a delegate.";
355 }
356
357 // Display a popup menu for WebKit using Cocoa widgets.
358 void WebContentsViewMac::ShowPopupMenu(
359 const gfx::Rect& bounds,
360 int item_height,
361 double item_font_size,
362 int selected_item,
363 const std::vector<WebMenuItem>& items,
364 bool right_aligned) {
365 PopupMenuHelper popup_menu_helper(web_contents_->GetRenderViewHost());
366 popup_menu_helper.ShowPopupMenu(bounds, item_height, item_font_size,
367 selected_item, items, right_aligned);
368 }
369
370 bool WebContentsViewMac::IsEventTracking() const {
371 return base::MessagePumpMac::IsHandlingSendEvent();
372 }
373
374 // Arrange to call CloseTab() after we're back to the main event loop.
375 // The obvious way to do this would be PostNonNestableTask(), but that
376 // will fire when the event-tracking loop polls for events. So we
377 // need to bounce the message via Cocoa, instead.
378 void WebContentsViewMac::CloseTabAfterEventTracking() {
379 [cocoa_view_ cancelDeferredClose];
380 [cocoa_view_ performSelector:@selector(closeTabAfterEvent)
381 withObject:nil
382 afterDelay:0.0];
383 }
384
385 void WebContentsViewMac::GetViewBounds(gfx::Rect* out) const {
386 // This method is not currently used on mac.
387 NOTIMPLEMENTED();
388 }
389
390 void WebContentsViewMac::CloseTab() {
391 web_contents_->Close(web_contents_->GetRenderViewHost());
392 }
393
394 @implementation WebContentsViewCocoa
395
396 - (id)initWithWebContentsViewMac:(WebContentsViewMac*)w {
397 self = [super initWithFrame:NSZeroRect];
398 if (self != nil) {
399 webContentsView_ = w;
400 dragDest_.reset(
401 [[WebDragDest alloc] initWithWebContentsImpl:[self webContents]]);
402 [self registerDragTypes];
403
404 [[NSNotificationCenter defaultCenter]
405 addObserver:self
406 selector:@selector(viewDidBecomeFirstResponder:)
407 name:kViewDidBecomeFirstResponder
408 object:nil];
409
410 if (webContentsView_->delegate()) {
411 [dragDest_ setDragDelegate:webContentsView_->delegate()->
412 GetDragDestDelegate()];
413 webContentsView_->delegate()->NativeViewCreated(self);
414 }
415 }
416 return self;
417 }
418
419 - (void)dealloc {
420 if (webContentsView_ && webContentsView_->delegate())
421 webContentsView_->delegate()->NativeViewDestroyed(self);
422
423 // Cancel any deferred tab closes, just in case.
424 [self cancelDeferredClose];
425
426 // This probably isn't strictly necessary, but can't hurt.
427 [self unregisterDraggedTypes];
428
429 [[NSNotificationCenter defaultCenter] removeObserver:self];
430
431 [super dealloc];
432 }
433
434 // Registers for the view for the appropriate drag types.
435 - (void)registerDragTypes {
436 NSArray* types = [NSArray arrayWithObjects:NSStringPboardType,
437 NSHTMLPboardType, NSURLPboardType, nil];
438 [self registerForDraggedTypes:types];
439 }
440
441 - (void)setCurrentDragOperation:(NSDragOperation)operation {
442 [dragDest_ setCurrentOperation:operation];
443 }
444
445 - (WebContentsImpl*)webContents {
446 if (webContentsView_ == NULL)
447 return NULL;
448 return webContentsView_->web_contents();
449 }
450
451 - (void)mouseEvent:(NSEvent*)theEvent {
452 WebContentsImpl* webContents = [self webContents];
453 if (webContents && webContents->GetDelegate()) {
454 NSPoint location = [NSEvent mouseLocation];
455 if ([theEvent type] == NSMouseMoved)
456 webContents->GetDelegate()->ContentsMouseEvent(
457 webContents, gfx::Point(location.x, location.y), true);
458 if ([theEvent type] == NSMouseExited)
459 webContents->GetDelegate()->ContentsMouseEvent(
460 webContents, gfx::Point(location.x, location.y), false);
461 }
462 }
463
464 - (BOOL)mouseDownCanMoveWindow {
465 // This is needed to prevent mouseDowns from moving the window
466 // around. The default implementation returns YES only for opaque
467 // views. WebContentsViewCocoa does not draw itself in any way, but
468 // its subviews do paint their entire frames. Returning NO here
469 // saves us the effort of overriding this method in every possible
470 // subview.
471 return NO;
472 }
473
474 - (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
475 [dragSource_ lazyWriteToPasteboard:sender
476 forType:type];
477 }
478
479 - (void)startDragWithDropData:(const WebDropData&)dropData
480 dragOperationMask:(NSDragOperation)operationMask
481 image:(NSImage*)image
482 offset:(NSPoint)offset {
483 dragSource_.reset([[WebDragSource alloc]
484 initWithContents:[self webContents]
485 view:self
486 dropData:&dropData
487 image:image
488 offset:offset
489 pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
490 dragOperationMask:operationMask]);
491 [dragSource_ startDrag];
492 }
493
494 // NSDraggingSource methods
495
496 // Returns what kind of drag operations are available. This is a required
497 // method for NSDraggingSource.
498 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
499 if (dragSource_.get())
500 return [dragSource_ draggingSourceOperationMaskForLocal:isLocal];
501 // No web drag source - this is the case for dragging a file from the
502 // downloads manager. Default to copy operation. Note: It is desirable to
503 // allow the user to either move or copy, but this requires additional
504 // plumbing to update the download item's path once its moved.
505 return NSDragOperationCopy;
506 }
507
508 // Called when a drag initiated in our view ends.
509 - (void)draggedImage:(NSImage*)anImage
510 endedAt:(NSPoint)screenPoint
511 operation:(NSDragOperation)operation {
512 [dragSource_ endDragAt:screenPoint operation:operation];
513
514 // Might as well throw out this object now.
515 dragSource_.reset();
516 }
517
518 // Called when a drag initiated in our view moves.
519 - (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint {
520 [dragSource_ moveDragTo:screenPoint];
521 }
522
523 // Called when we're informed where a file should be dropped.
524 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest {
525 if (![dropDest isFileURL])
526 return nil;
527
528 NSString* file_name = [dragSource_ dragPromisedFileTo:[dropDest path]];
529 if (!file_name)
530 return nil;
531
532 return [NSArray arrayWithObject:file_name];
533 }
534
535 // NSDraggingDestination methods
536
537 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
538 return [dragDest_ draggingEntered:sender view:self];
539 }
540
541 - (void)draggingExited:(id<NSDraggingInfo>)sender {
542 [dragDest_ draggingExited:sender];
543 }
544
545 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
546 return [dragDest_ draggingUpdated:sender view:self];
547 }
548
549 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
550 return [dragDest_ performDragOperation:sender view:self];
551 }
552
553 - (void)cancelDeferredClose {
554 SEL aSel = @selector(closeTabAfterEvent);
555 [NSObject cancelPreviousPerformRequestsWithTarget:self
556 selector:aSel
557 object:nil];
558 }
559
560 - (void)clearTabContentsView {
561 webContentsView_ = NULL;
562 }
563
564 - (void)closeTabAfterEvent {
565 webContentsView_->CloseTab();
566 }
567
568 - (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
569 NSView* view = [notification object];
570 if (![[self subviews] containsObject:view])
571 return;
572
573 NSSelectionDirection direction =
574 [[[notification userInfo] objectForKey:kSelectionDirection]
575 unsignedIntegerValue];
576 if (direction == NSDirectSelection)
577 return;
578
579 [self webContents]->
580 FocusThroughTabTraversal(direction == NSSelectingPrevious);
581 }
582
583 @end
OLDNEW
« no previous file with comments | « content/browser/tab_contents/web_contents_view_mac.h ('k') | content/browser/tab_contents/web_contents_view_mac_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698