|  | // Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/ui/ime/ime_window.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "chrome/browser/chrome_notification_types.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/ui/ime/ime_native_window.h" | 
|  | #include "chrome/browser/ui/ime/ime_window_observer.h" | 
|  | #include "content/public/browser/notification_service.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/render_process_host.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "extensions/common/constants.h" | 
|  | #include "extensions/common/extension.h" | 
|  | #include "extensions/common/manifest_handlers/icons_handler.h" | 
|  | #include "ui/display/display.h" | 
|  | #include "ui/display/display_finder.h" | 
|  | #include "ui/display/screen.h" | 
|  | #include "ui/gfx/image/image.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // The vertical margin between the cursor and the follow-cursor window. | 
|  | const int kFollowCursorMargin = 3; | 
|  |  | 
|  | // The offset from the left of follow cursor window to the left of cursor. | 
|  | const int kFollowCursorOffset = 32; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | namespace ui { | 
|  |  | 
|  | ImeWindow::ImeWindow(Profile* profile, | 
|  | const extensions::Extension* extension, | 
|  | content::RenderFrameHost* opener_render_frame_host, | 
|  | const std::string& url, | 
|  | Mode mode, | 
|  | const gfx::Rect& bounds) | 
|  | : mode_(mode), native_window_(nullptr) { | 
|  | if (extension) {  // Allow nullable |extension| for testability. | 
|  | title_ = extension->name(); | 
|  | icon_.reset(new extensions::IconImage( | 
|  | profile, extension, extensions::IconsInfo::GetIcons(extension), | 
|  | extension_misc::EXTENSION_ICON_BITTY, gfx::ImageSkia(), this)); | 
|  | } | 
|  |  | 
|  | registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, | 
|  | content::NotificationService::AllSources()); | 
|  |  | 
|  | GURL gurl(url); | 
|  | if (!gurl.is_valid()) | 
|  | gurl = extension->GetResourceURL(url); | 
|  |  | 
|  | scoped_refptr<content::SiteInstance> site_instance = | 
|  | opener_render_frame_host ? opener_render_frame_host->GetSiteInstance() | 
|  | : nullptr; | 
|  | if (!site_instance || | 
|  | site_instance->GetSiteURL().GetOrigin() != gurl.GetOrigin()) { | 
|  | site_instance = content::SiteInstance::CreateForURL(profile, gurl); | 
|  | } | 
|  | content::WebContents::CreateParams create_params(profile, | 
|  | std::move(site_instance)); | 
|  | if (opener_render_frame_host) { | 
|  | create_params.opener_render_process_id = | 
|  | opener_render_frame_host->GetProcess()->GetID(); | 
|  | create_params.opener_render_frame_id = | 
|  | opener_render_frame_host->GetRoutingID(); | 
|  | } | 
|  | web_contents_.reset(content::WebContents::Create(create_params)); | 
|  | web_contents_->SetDelegate(this); | 
|  | content::OpenURLParams params(gurl, content::Referrer(), | 
|  | WindowOpenDisposition::SINGLETON_TAB, | 
|  | ui::PAGE_TRANSITION_LINK, false); | 
|  | web_contents_->OpenURL(params); | 
|  |  | 
|  | native_window_ = CreateNativeWindow(this, bounds, web_contents_.get()); | 
|  | } | 
|  |  | 
|  | void ImeWindow::Show() { | 
|  | native_window_->Show(); | 
|  | } | 
|  |  | 
|  | void ImeWindow::Hide() { | 
|  | native_window_->Hide(); | 
|  | } | 
|  |  | 
|  | void ImeWindow::Close() { | 
|  | web_contents_.reset(); | 
|  | native_window_->Close(); | 
|  | } | 
|  |  | 
|  | void ImeWindow::SetBounds(const gfx::Rect& bounds) { | 
|  | native_window_->SetBounds(bounds); | 
|  | } | 
|  |  | 
|  | void ImeWindow::FollowCursor(const gfx::Rect& cursor_bounds) { | 
|  | if (mode_ != FOLLOW_CURSOR) | 
|  | return; | 
|  |  | 
|  | gfx::Rect screen_bounds = | 
|  | display::FindDisplayNearestPoint( | 
|  | display::Screen::GetScreen()->GetAllDisplays(), | 
|  | gfx::Point(cursor_bounds.x(), cursor_bounds.y()))->bounds(); | 
|  | gfx::Rect window_bounds = native_window_->GetBounds(); | 
|  | int screen_width = screen_bounds.x() + screen_bounds.width(); | 
|  | int screen_height = screen_bounds.y() + screen_bounds.height(); | 
|  | int width = window_bounds.width(); | 
|  | int height = window_bounds.height(); | 
|  | // By default, aligns the left of the window client area to the left of the | 
|  | // cursor, and aligns the top of the window to the bottom of the cursor. | 
|  | // If the right of the window would go beyond the screen bounds, aligns the | 
|  | // right of the window to the screen bounds. | 
|  | // If the bottom of the window would go beyond the screen bounds, aligns the | 
|  | // bottom of the window to the cursor top. | 
|  | int x = cursor_bounds.x() - kFollowCursorOffset; | 
|  | int y = cursor_bounds.y() + cursor_bounds.height() + kFollowCursorMargin; | 
|  | if (width < screen_width && x + width > screen_width) | 
|  | x = screen_width - width; | 
|  | if (height < screen_height && y + height > screen_height) | 
|  | y = cursor_bounds.y() - height - kFollowCursorMargin; | 
|  | window_bounds.set_x(x); | 
|  | window_bounds.set_y(y); | 
|  | SetBounds(window_bounds); | 
|  | } | 
|  |  | 
|  | int ImeWindow::GetFrameId() const { | 
|  | return web_contents_->GetMainFrame()->GetRoutingID(); | 
|  | } | 
|  |  | 
|  | void ImeWindow::OnWindowDestroyed() { | 
|  | for (ImeWindowObserver& observer : observers_) | 
|  | observer.OnWindowDestroyed(this); | 
|  | native_window_ = nullptr; | 
|  | delete this; | 
|  | } | 
|  |  | 
|  | void ImeWindow::AddObserver(ImeWindowObserver* observer) { | 
|  | observers_.AddObserver(observer); | 
|  | } | 
|  |  | 
|  | void ImeWindow::RemoveObserver(ImeWindowObserver* observer) { | 
|  | observers_.RemoveObserver(observer); | 
|  | } | 
|  |  | 
|  | void ImeWindow::OnExtensionIconImageChanged(extensions::IconImage* image) { | 
|  | if (native_window_) | 
|  | native_window_->UpdateWindowIcon(); | 
|  | } | 
|  |  | 
|  | ImeWindow::~ImeWindow() {} | 
|  |  | 
|  | void ImeWindow::Observe(int type, | 
|  | const content::NotificationSource& source, | 
|  | const content::NotificationDetails& details) { | 
|  | DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); | 
|  | Close(); | 
|  | } | 
|  |  | 
|  | content::WebContents* ImeWindow::OpenURLFromTab( | 
|  | content::WebContents* source, | 
|  | const content::OpenURLParams& params) { | 
|  | source->GetController().LoadURL(params.url, params.referrer, | 
|  | params.transition, params.extra_headers); | 
|  | return source; | 
|  | } | 
|  |  | 
|  | bool ImeWindow::CanDragEnter( | 
|  | content::WebContents* source, | 
|  | const content::DropData& data, | 
|  | blink::WebDragOperationsMask operations_allowed) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void ImeWindow::CloseContents(content::WebContents* source) { | 
|  | Close(); | 
|  | } | 
|  |  | 
|  | void ImeWindow::MoveContents(content::WebContents* source, | 
|  | const gfx::Rect& pos) { | 
|  | if (!native_window_) | 
|  | return; | 
|  |  | 
|  | if (mode_ == NORMAL) { | 
|  | native_window_->SetBounds(pos); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Follow-cursor window needs to remain the x/y and only allow JS to | 
|  | // change the size. | 
|  | gfx::Rect bounds = native_window_->GetBounds(); | 
|  | bounds.set_width(pos.width()); | 
|  | bounds.set_height(pos.height()); | 
|  | native_window_->SetBounds(bounds); | 
|  | } | 
|  |  | 
|  | bool ImeWindow::IsPopupOrPanel(const content::WebContents* source) const { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace ui |