Window Contents Capturing using WM_PRINT Message

本文介绍了一种使用WM_PRINT和WM_PRINTCLIENT消息捕获完全可见、部分可见或完全不可见窗口内容的方法。通过巧妙地处理WM_PRINTCLIENT消息,即使没有源代码也能使应用程序窗口正确响应这些消息。

http://www.fengyuan.com/article/wmprint.html

Problem

The normal way of capturing the contents of a window into a bitmap is creating a memory device context (CreateCompatibleDC ), creating a device-dependent bitmap (CreateCompatibleBitmap ) or a DIB section (CreateDIBSection ), selecting the bitmap into the memory DC (SelectObject ), and then bitblt from the window's device context (GetWindowDC ) into the memory DC (Bitblt ). After that, a copy of the contents of the window as it appears on the screen is stored in the bitmap.

But what if the window is hidden, or partially blocked with other windows ? When a window is hidden or partially blocked, the non-visible part of the window will be clipped in the device context returned from GetWindowDC. In other words, that part of the window can't be captured using a simple BitBlt .

To capture any window, completely visible, partially visible, or complete invisible, Win32 API provides two special messages, WM_PRINT and WM_PRINTCLIENT. Both these messages take a device context handle as a parameter, and the window handling these messages is supposed to draw itself or its client area into the device context provided.

Sounds good ? There is a catch. Normally only windows implemented by the operating system are knowledgeable to handle these messages. If you send a WM_PRINT message to a window, normally all the non-client area, which includes border, title bar, menu bar, scroll bar, etc., and common controls are drawn properly. Client area of windows implemented by application programs are normally left blank.

This article shows a sophisticated method to trick a window implemented by applications to handle WM_PRINTCLIENT message without its source code.

Test Program

To experiment with WM_PRINT/WM_PRINTCLIENT messages, a text program is written. Actually, the program is a slightly modified version of the "Hello, World" program generated by MSVC Wizard (plain Win32 application).

The routine handling WM_PAINT message is OnPaint, which calls BeginPaint, a custom drawing routine OnDraw, and then EndPaint. The OnDraw routine just draws an ellipse in the client area. Here is how its screen looks.

.

void OnDraw(HWND hWnd, HDC hDC)
{
    RECT rt;
    GetClientRect(hWnd, &rt);

    SelectObject(hDC, GetSysColorBrush(COLOR_INFOBK));
    Ellipse(hDC, rt.left+5, rt.top+5, rt.right-3, rt.bottom-5);
}

void OnPaint(HWND hWnd)
{
    PAINTSTRUCT ps;
	
    HDC hDC = BeginPaint(hWnd, & ps);

    OnDraw(hWnd, hDC);
    EndPaint(hWnd, & ps);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) 
    {
	case WM_COMMAND:
	    switch ( LOWORD(wParam) )
	    {
		case IDM_EXIT:
		    DestroyWindow(hWnd);
		    break;

                case ID_FILE_PRINT:
                    PrintWindow(hWnd);
                    break;

		default:
		   return DefWindowProc(hWnd, message, wParam, lParam);
	    }
	    break;

	case WM_PAINT:
            OnPaint(hWnd);
            break;
		
        case WM_DESTROY:
	    PostQuitMessage(0);
	    break;

        default:
   	    return DefWindowProc(hWnd, message, wParam, lParam);
   }

   return 0;
}


To test the WM_PRINT/WM_PRINTCLIENT message, a "Print" menu item is added to the "File" menu. The window procedure calls the following PrintWindow routine to capture the screen using WM_PRINT message. Note the WM_PRINT message creates a memory DC, a DDB, selects the DDB into the memory DC, and then passes the memory DC handle as the WPARAM of the WM_PRINT message. The LPARAM parameter of the message specifies that everything should be drawn, including client/non-client area, background, and any child window.

void PrintWindow(HWND hWnd)
{
    HDC hDCMem = CreateCompatibleDC(NULL);

    RECT rect;

    GetWindowRect(hWnd, & rect);

    HBITMAP hBmp = NULL;

    {
        HDC hDC = GetDC(hWnd);
        hBmp = CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top);
        ReleaseDC(hWnd, hDC);
    }

    HGDIOBJ hOld = SelectObject(hDCMem, hBmp);
    SendMessage(hWnd, WM_PRINT, (WPARAM) hDCMem, PRF_CHILDREN | PRF_CLIENT | PRF_ERASEBKGND | PRF_NONCLIENT | PRF_OWNED);

    SelectObject(hDCMem, hOld);
    DeleteObject(hDCMem);

    OpenClipboard(hWnd);

    EmptyClipboard(); 
    SetClipboardData(CF_BITMAP, hBmp);
    CloseClipboard();
}


When WM_PRINT message returns, the bitmap is pasted to the clipboard, so you can use any graphics application to view/save the image. Here is what's being captured, everything except the ellipse.

Prototype Solution

The problem with the test program shown above is of course the disconnection between the WM_PRINT message, and the OnPaint routine handling WM_PAINT message. The default window procedure is smart enough to draw non-client area and then send a WM_PRINTCLIENT message to the window. But there is no default processing for the WM_PRINTCLIENT message, which explains why only the client are is left blank.

If you have the source code of the window procedure, adding a handling of WM_PRINTCLIENT to share the WM_PAINT message handling is very easy, as is shown below.

void OnPaint(HWND hWnd

, WPARAM wParam

)
{
    PAINTSTRUCT ps;
    HDC         hDC;

    

if ( wParam==0 )


        hDC = BeginPaint(hWnd, & ps);
    

else
        hDC = (HDC) wParam;



    OnDraw(hWnd, hDC);

    

if ( wParam==0 )


        EndPaint(hWnd, & ps);
}

LRESULT CALLBACK WndProc0(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        ...
        

case WM_PRINTCLIENT:
            SendMessage(hWnd, WM_PAINT, wParam, lParam);
            break;



	case WM_PAINT:
            OnPaint(hWnd

, wParam

);
    	break;
	...
   }
   return 0;
}


In the code shown above, handling for the WM_PRINTCLIENT is added, which just sends a WM_PAINT message to the window itself with the wParam and lParam. The WPARAM parameter is added to OnPaint routine. When wParam is not 0, it's cast into a device context handle, instead of calling BeginPaint to retrive a device context for the non-client area. Likewise, EndPaint is only called when wParam is 0. These simple changes in source code level makes the whole WM_PRINT message handling complete for client area.

Handling WM_PRINTCLIENT Message without Source Code Change

What if you do not have the source code of the window ? Subclassing the window to add a handling for the WM_PRINTCLIENT is easy. What's hard is how to trick bypass BeginPaint and EndPaint, and how to pass the wParam from WM_PRINTCLIENT to the drawing code after BeginPaint.

Here is the declaraction of the CPaintHook class which handles window subclassing and implementation of WM_PRINTCLIENT message handling.

// Copyright (C) 2000 by Feng Yuan (www.fengyuan.com)

class CPaintHook
{
    BYTE      m_thunk[9];
    WNDPROC   m_OldWndProc;
    HDC       m_hDC;

    static HDC  WINAPI MyBeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
    static BOOL WINAPI MyEndPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);

    virtual LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	
public:
    
    bool Within_WM_PRINT(void) const
    {  
        if ( (m_thunk[0]==0xB9) && ((* (unsigned *) (m_thunk+5))==0x20FF018B) )
            return m_hDC !=0;
        else
            return false;
    }

    void SubClass(HWND hWnd);
};

The CPaintHook class has three member variables, one BYTE array of storing some machine code, a pointer to the original window procedure, and a device context handle. The two static methods replaces the original system provided BeginPaint and EndPaint routines. A virtual window message procedure is provided to override message processing for the window. Finally, the SubClass method subclasses an existing window and makes sure it handles WM_PRINTCLIENT message properly.

The implementation of this seemly simple class is quite tricky. Some knowledge of Win32 API implementation, compiler code generation, Intel machine code, and virtual memory is needed to understand it fully.

// Copyright (C) 2000 by Feng Yuan (www.fengyuan.com)

#include "stdafx.h"
#include <assert.h>

#include "hookpaint.h"

bool Hook(const TCHAR * module, const TCHAR * proc, unsigned & syscall_id, BYTE * & pProc, const void * pNewProc)
{
    HINSTANCE hMod = GetModuleHandle(module);

    pProc = (BYTE *) GetProcAddress(hMod, proc);

    if ( pProc[0] == 0xB8 )
    {
        syscall_id = * (unsigned *) (pProc + 1);

        DWORD flOldProtect;

        VirtualProtect(pProc, 5, PAGE_EXECUTE_READWRITE, & flOldProtect);

        pProc[0] = 0xE9;
        * (unsigned *) (pProc+1) = (unsigned)pNewProc - (unsigned) (pProc+5);

        pProc += 5;

        return true;
    }
    else
        return false;
}

The Hook routine hooks certain kind of exported function from a module by directly modifying its starting machine code. The benefit of hacking machine code directly is that you only need to hack into a single place, all the call in a process is taken care of. But hacking machine code directly is very tricky because it's not easy to parse machine code to find extra space for a five byte jump instruction. Chapter 4 of my book contains more generic code to handle this problem. What's shown here only applies to a special case, which applies to BeginPaint and EndPaint on Windows NT/2000 machines. On these machine, BeginPaint and EndPaint calls system services provided by Win32K.SYS . These routines follow a strict pattern, the first instruction stores a DWORD index into the EAX register. The instructions after that issue a software interruption (0x2E), which will be served by Win32K.SYS in kernel mode address space.

The Hook routine uses GetModuleHandle to retrieve module handle, GetProcAddress to retrieve the address of an exported Win32 API function. It then checks if the first instruction is a constant move to EAX register instruction (0xB8). If a match is found, VirtualProtect is used to change the protection flag for that page to PAGE_EXECUTE_READWRITE, which makes it writeable. The system service call index is saved, and then the first five bytes are changed to a jump instruction to a function whose address is passed through the pNewProc parameter.

LRESULT CPaintHook::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    assert(m_OldWndProc);

    if ( uMsg==WM_PRINTCLIENT )
    {
        m_hDC = (HDC) wParam;
        uMsg  = WM_PAINT;
    }

    LRESULT hRslt = CallWindowProc(m_OldWndProc, hWnd, uMsg, wParam, lParam);

    m_hDC = NULL;

    return hRslt;
}

Implementing CPaintHook::WndProc is fairly simple. If the current message is WM_PRINTCLIENT, the device context handle passed in WPARAM is saved in member variable m_hDC, and then message is changed to WM_PAINT. CallWindowProc is used to call the original window procedure.

HDC WINAPI CPaintHook::MyBeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint)
{
    const CPaintHook * pThis = (CPaintHook *) GetWindowLong(hWnd, GWL_WNDPROC);
    
    pThis = (const CPaintHook *) ( (unsigned) pThis - (unsigned) & pThis->m_thunk[0] + (unsigned) pThis );
    
    if ( pThis->Within_WM_PRINT() )
    {
        memset(lpPaint, 0, sizeof(PAINTSTRUCT));

        lpPaint->hdc = pThis->m_hDC;
        
        GetClientRect(hWnd, & lpPaint->rcPaint);
        
        return pThis->m_hDC;
    }
    else
    {
        __asm   mov     eax, syscall_BeginPaint
        __asm   push    lpPaint
        __asm   push    hWnd
        __asm   call    pBeginPaint
    }
}

BOOL WINAPI CPaintHook::MyEndPaint(HWND hWnd, LPPAINTSTRUCT lpPaint)
{
    const CPaintHook * pThis = (CPaintHook *) GetWindowLong(hWnd, GWL_WNDPROC);
    
    pThis = (const CPaintHook *) ( (unsigned) pThis - (unsigned) & pThis->m_thunk[0] + (unsigned) pThis );
    
    if ( pThis->Within_WM_PRINT() )
        return TRUE;
    else
    {
        __asm   mov     eax, syscall_EndPaint
        __asm   push    lpPaint
        __asm   push    hWnd
        __asm   call    pEndPaint
    }
}

Implementation of the two static functions, MyBeginPaint and MyEndPaint, are very similar. Beging static member functions, they do not have 'this' pointer to access object member variables. The two functions calculates the current 'this' pointer from the current window procedure address, which is the address of its m_thunk member variable (explained below). Once 'this' pointer is got, the m_hDC member variable is changed to see if we're actually handling a WM_PRINTCLIENT message, instead of normal WM_PAINT message. If a device context handle is given, the original BeginPaint and EndPaint will be skipped. Otherwise, the system service index is set into the EAX register, and the instructions after the first instruction in the original BeginPaint/EndPaint is called as a subroutine, although a jump instruction without pusing the parameters will work too.

static unsigned syscall_BeginPaint = 0;
static BYTE *   pBeginPaint        = NULL;

static unsigned syscall_EndPaint   = 0;
static BYTE *   pEndPaint          = NULL;

CPaintHook::CPaintHook()
{
    static bool s_hooked = false;

    if ( ! s_hooked )
    {
        Hook("USER32.DLL", "BeginPaint", syscall_BeginPaint, pBeginPaint, MyBeginPaint);
        Hook("USER32.DLL", "EndPaint",   syscall_EndPaint,   pEndPaint,   MyEndPaint);

        s_hooked = true;
    }

    m_thunk[0]              = 0xB9;	     // mov ecx, 
    *((DWORD *)(m_thunk+1)) = (DWORD) this;  //          this
    *((DWORD *)(m_thunk+5)) = 0x20FF018B;    // mov eax, [ecx] 

    m_OldWndProc = NULL;
    m_hDC        = NULL;
}

void CPaintHook::SubClass(HWND hWnd)
{		
    m_OldWndProc = (WNDPROC) GetWindowLong(hWnd, GWL_WNDPROC);
    SetWindowLong(hWnd, GWL_WNDPROC, (LONG) ((void *) m_thunk));
}

The constructor CPaintHook::CPaintHook and the SubClass method are the magic glue which hold everything together. The constructor will make sure the Hook function are called twice to hook Win32 API function BeginPaint and EndPaint, which are both exported from module USER32.DLL. For each instrance of the CPaintHook class, it's m_thunk data member will be initialized to two machine instructions. The first moves 'this' pointer to the ECX register, the second calls the first virtual method of that object, the CPaintHook::WndProc virtual method implementation.

The SubClass method remembers the original window procedure, and passes the address of m_thunk data member as the new window procedure.

With the CPaintHook class, hooking a window to handle WM_PRINTCLIENT message is a piece of cake. Here is the WinMain function of our test program, which creates an instance of the CPaintHook class on the stack, and calls the SubClass method.

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    WNDCLASSEX wcex;

    memset(&wcex, 0, sizeof(wcex));
	
    wcex.cbSize         = sizeof(WNDCLASSEX); 
    wcex.style		= CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = (WNDPROC) WndProc;
    wcex.hInstance	= hInstance;
    wcex.hCursor	= LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = (LPCSTR) IDC_PAINT;
    wcex.lpszClassName  = "Class";

    RegisterClassEx(&wcex);

    HWND hWnd = CreateWindow("Class", "WM_PRINT", WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    assert(hWnd);

    CPaintHook hook;

    hook.SubClass(hWnd);



    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
   	
    while (GetMessage(&msg, NULL, 0, 0)) 
    {
	TranslateMessage(&msg);
	DispatchMessage(&msg);
    }

    return msg.wParam;
}

Sample Program

Sample program using WIN32 API wmprint.zip

Sample program for capturing windows of other processes using DLL injection: capture.zip

Limitation

  • The Hook function only handles exported function whose first instruction is "MOV EAX, <DWORD_constant>". So the implementation shown here only applies to Windows NT/2000. Refer to Chapter 4 for more generic of restrictive API hooking solutions.
  • Only tested on Windows 2000 machine.
chromium源码中下面的函数具体功能,详细解释一下: base::WeakPtr<content::NavigationHandle> Navigate(NavigateParams* params) { TRACE_EVENT1("navigation", "chrome::Navigate", "disposition", params->disposition); Browser* source_browser = params->browser; if (source_browser) { params->initiating_profile = source_browser->profile(); } DCHECK(params->initiating_profile); // If the created window is a partitioned popin, a valid source exists, and // the disposition is NEW_POPUP then the resulting popup should be tab-modal. // See: https://explainers-by-googlers.github.io/partitioned-popins/ params->is_tab_modal_popup |= params->window_features.is_partitioned_popin && params->source_contents && params->disposition == WindowOpenDisposition::NEW_POPUP; #if BUILDFLAG(IS_CHROMEOS) if (params->initiating_profile->IsOffTheRecord() && params->initiating_profile->GetOTRProfileID().IsCaptivePortal() && params->disposition != WindowOpenDisposition::NEW_POPUP && params->disposition != WindowOpenDisposition::CURRENT_TAB && !IncognitoModeForced(params->initiating_profile)) { // Navigation outside of the current tab or the initial popup window from a // captive portal signin window should be prevented. params->disposition = WindowOpenDisposition::CURRENT_TAB; } #endif if (params->initiating_profile->ShutdownStarted()) { // Don't navigate when the profile is shutting down. return nullptr; } // Block navigation requests when in locked fullscreen mode. We allow // navigation requests in the webapp when locked for OnTask (only relevant for // non-web browser scenarios). // TODO(b/365146870): Remove once we consolidate locked fullscreen with // OnTask. if (source_browser) { bool should_block_navigation = platform_util::IsBrowserLockedFullscreen(source_browser); #if BUILDFLAG(IS_CHROMEOS) if (source_browser->IsLockedForOnTask()) { should_block_navigation = false; } #endif // BUILDFLAG(IS_CHROMEOS) if (should_block_navigation) { return nullptr; } } // Open System Apps in their standalone window if necessary. // TODO(crbug.com/40136163): Remove this code after we integrate with intent // handling. #if BUILDFLAG(IS_CHROMEOS) const std::optional<ash::SystemWebAppType> capturing_system_app_type = ash::GetCapturingSystemAppForURL(params->initiating_profile, params->url); if (capturing_system_app_type && (!params->browser || !ash::IsBrowserForSystemWebApp(params->browser, capturing_system_app_type.value()))) { ash::SystemAppLaunchParams swa_params; swa_params.url = params->url; ash::LaunchSystemWebAppAsync(params->initiating_profile, capturing_system_app_type.value(), swa_params); // It's okay to early return here, because LaunchSystemWebAppAsync uses a // different logic to choose (and create if necessary) a browser window for // system apps. // // It's okay to skip the checks and cleanups below. The link captured system // app will either open in its own browser window, or navigate an existing // browser window exclusively used by this app. For the initiating browser, // the navigation should appear to be cancelled. return nullptr; } #endif // BUILDFLAG(IS_CHROMEOS) #if !BUILDFLAG(IS_ANDROID) // Force isolated PWAs to open in an app window. params->force_open_pwa_window = content::SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel( params->initiating_profile, params->url); params->open_pwa_window_if_possible |= params->force_open_pwa_window; #endif if (!AdjustNavigateParamsForURL(params)) { return nullptr; } // Picture-in-picture browser windows must have a source contents in order for // the window to function correctly. If we have no source contents to work // with (e.g. if an extension popup attempts to open a PiP window), we should // cancel the navigation. The source URL must also be of a type that's // allowed to open document PiP. See `PictureInPictureWindowManager` for // details on what's allowed. if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { const GURL& url = params->source_contents ? params->source_contents->GetLastCommittedURL() : GURL(); if (!PictureInPictureWindowManager::IsSupportedForDocumentPictureInPicture( url)) { return nullptr; } } // If no source WebContents was specified, we use the selected one from the // target browser. This must happen before GetBrowserAndTabForDisposition() // has a chance to replace |params->browser| with another one, but after the // above check that relies on the original source_contents value. if (!params->source_contents && params->browser) { params->source_contents = params->browser->tab_strip_model()->GetActiveWebContents(); } WebContents* contents_to_navigate_or_insert = params->contents_to_insert.get(); if (params->switch_to_singleton_tab) { DCHECK_EQ(params->disposition, WindowOpenDisposition::SINGLETON_TAB); contents_to_navigate_or_insert = params->switch_to_singleton_tab; } #if !BUILDFLAG(IS_ANDROID) // If this is a Picture in Picture window, then notify the pip manager about // it. This enables the opener and pip window to stay connected, so that (for // example), the pip window does not outlive the opener. // // We do this before creating the browser window, so that the browser can talk // to the PictureInPictureWindowManager. Otherwise, the manager has no idea // that there's a pip window. if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { // Picture in picture windows may not be opened by other picture in // picture windows, or without an opener. if (!params->browser || params->browser->is_type_picture_in_picture()) { params->browser = nullptr; return nullptr; } PictureInPictureWindowManager::GetInstance()->EnterDocumentPictureInPicture( params->source_contents, contents_to_navigate_or_insert); } #endif // !BUILDFLAG(IS_ANDROID) // TODO(crbug.com/364657540): Revisit integration with web_application system // later if needed. int singleton_index; #if !BUILDFLAG(IS_ANDROID) std::unique_ptr<web_app::NavigationCapturingProcess> app_navigation = web_app::NavigationCapturingProcess::MaybeHandleAppNavigation(*params); std::optional<std::tuple<Browser*, int>> app_browser_tab_override; if (app_navigation) { app_browser_tab_override = app_navigation->GetInitialBrowserAndTabOverrideForNavigation(*params); } std::tie(params->browser, singleton_index) = app_browser_tab_override.has_value() ? *app_browser_tab_override : GetBrowserAndTabForDisposition(*params); #else // !BUILDFLAG(IS_ANDROID) std::tie(params->browser, singleton_index) = GetBrowserAndTabForDisposition(*params); #endif if (!params->browser) { return nullptr; } // Trying to open a background tab when in a non-tabbed app browser results in // focusing a regular browser window and opening a tab in the background // of that window. Change the disposition to NEW_FOREGROUND_TAB so that // the new tab is focused. if (source_browser && source_browser->is_type_app() && params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB && !(source_browser->app_controller() && source_browser->app_controller()->has_tab_strip())) { params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; } if (singleton_index != -1) { contents_to_navigate_or_insert = params->browser->tab_strip_model()->GetWebContentsAt(singleton_index); } else if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) { // The user is trying to open a tab that no longer exists. If we open a new // tab, it could leave orphaned NTPs around, but always overwriting the // current tab could could clobber state that the user was trying to // preserve. Fallback to the behavior used for singletons: overwrite the // current tab if it's the NTP, otherwise open a new tab. params->disposition = WindowOpenDisposition::SINGLETON_TAB; ShowSingletonTabOverwritingNTP(params); return nullptr; } if (params->force_open_pwa_window) { CHECK(web_app::AppBrowserController::IsWebApp(params->browser)); } #if BUILDFLAG(IS_CHROMEOS) if (source_browser && source_browser != params->browser) { // When the newly created browser was spawned by a browser which visits // another user's desktop, it should be shown on the same desktop as the // originating one. (This is part of the desktop separation per profile). auto* window_manager = MultiUserWindowManagerHelper::GetWindowManager(); // Some unit tests have no client instantiated. if (window_manager) { aura::Window* src_window = source_browser->window()->GetNativeWindow(); aura::Window* new_window = params->browser->window()->GetNativeWindow(); const AccountId& src_account_id = window_manager->GetUserPresentingWindow(src_window); if (src_account_id != window_manager->GetUserPresentingWindow(new_window)) { // Once the window gets presented, it should be shown on the same // desktop as the desktop of the creating browser. Note that this // command will not show the window if it wasn't shown yet by the // browser creation. window_manager->ShowWindowForUser(new_window, src_account_id); } } } #endif // Navigate() must not return early after this point. if (GetSourceProfile(params) != params->browser->profile()) { // A tab is being opened from a link from a different profile, we must reset // source information that may cause state to be shared. params->opener = nullptr; params->source_contents = nullptr; params->source_site_instance = nullptr; params->referrer = content::Referrer(); } // Make sure the Browser is shown if params call for it. ScopedBrowserShower shower(params, &contents_to_navigate_or_insert); if (params->is_tab_modal_popup) { shower.set_source_contents(params->source_contents); } // Some dispositions need coercion to base types. NormalizeDisposition(params); // If a new window has been created, it needs to be shown. if (params->window_action == NavigateParams::NO_ACTION && source_browser != params->browser && params->browser->tab_strip_model()->empty()) { params->window_action = NavigateParams::SHOW_WINDOW; } // If we create a popup window from a non user-gesture, don't activate it. if (params->window_action == NavigateParams::SHOW_WINDOW && params->disposition == WindowOpenDisposition::NEW_POPUP && params->user_gesture == false) { params->window_action = NavigateParams::SHOW_WINDOW_INACTIVE; } // Determine if the navigation was user initiated. If it was, we need to // inform the target WebContents, and we may need to update the UI. bool user_initiated = params->transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR || !ui::PageTransitionIsWebTriggerable(params->transition); base::WeakPtr<content::NavigationHandle> navigation_handle; std::unique_ptr<tabs::TabModel> tab_to_insert; if (params->contents_to_insert) { tab_to_insert = std::make_unique<tabs::TabModel>(std::move(params->contents_to_insert), params->browser->tab_strip_model()); } // If no target WebContents was specified (and we didn't seek and find a // singleton), we need to construct one if we are supposed to target a new // tab. if (!contents_to_navigate_or_insert) { DCHECK(!params->url.is_empty()); if (params->disposition != WindowOpenDisposition::CURRENT_TAB) { tab_to_insert = std::make_unique<tabs::TabModel>( CreateTargetContents(*params, params->url), params->browser->tab_strip_model()); contents_to_navigate_or_insert = tab_to_insert->GetContents(); apps::SetAppIdForWebContents(params->browser->profile(), contents_to_navigate_or_insert, params->app_id); #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) captive_portal::CaptivePortalTabHelper::FromWebContents( contents_to_navigate_or_insert) ->set_window_type(params->captive_portal_window_type); #endif } else { // ... otherwise if we're loading in the current tab, the target is the // same as the source. DCHECK(params->source_contents); contents_to_navigate_or_insert = params->source_contents; } // Try to handle non-navigational URLs that popup dialogs and such, these // should not actually navigate. if (!HandleNonNavigationAboutURL(params->url)) { // Perform the actual navigation, tracking whether it came from the // renderer. navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, params->url, params); } } else { // |contents_to_navigate_or_insert| was specified non-NULL, and so we assume // it has already been navigated appropriately. We need to do nothing more // other than add it to the appropriate tabstrip. } // If the user navigated from the omnibox, and the selected tab is going to // lose focus, then make sure the focus for the source tab goes away from the // omnibox. if (params->source_contents && (params->disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB || params->disposition == WindowOpenDisposition::NEW_WINDOW) && (params->tabstrip_add_types & AddTabTypes::ADD_INHERIT_OPENER)) { params->source_contents->Focus(); } if (tab_to_insert) { // Save data needed for link capturing into apps that cannot otherwise be // inferred later in the navigation. These are only needed when the // navigation happens in a different tab to the link click. apps::SetLinkCapturingSourceDisposition(tab_to_insert->GetContents(), params->disposition); } if (params->source_contents == contents_to_navigate_or_insert) { // The navigation occurred in the source tab. params->browser->UpdateUIForNavigationInTab( contents_to_navigate_or_insert, params->transition, params->window_action, user_initiated); } else if (singleton_index == -1) { if (source_browser != params->browser) { params->tabstrip_index = params->browser->tab_strip_model()->count(); } // If some non-default value is set for the index, we should tell the // TabStripModel to respect it. if (params->tabstrip_index != -1) { params->tabstrip_add_types |= AddTabTypes::ADD_FORCE_INDEX; } // Maybe notify that an open operation has been done from a gesture. // TODO(crbug.com/40719979): preferably pipe this information through the // TabStripModel instead. See bug for deeper discussion. if (params->user_gesture && source_browser == params->browser) { params->browser->window()->LinkOpeningFromGesture(params->disposition); } DCHECK(tab_to_insert); // The navigation should insert a new tab into the target Browser. params->browser->tab_strip_model()->AddTab( std::move(tab_to_insert), params->tabstrip_index, params->transition, params->tabstrip_add_types, params->group); } if (singleton_index >= 0) { // If switching browsers, make sure it is shown. if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB && params->browser != source_browser) { params->window_action = NavigateParams::SHOW_WINDOW; } if (contents_to_navigate_or_insert->IsCrashed()) { contents_to_navigate_or_insert->GetController().Reload( content::ReloadType::NORMAL, true); } else if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE && contents_to_navigate_or_insert->GetURL() != params->url) { navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, params->url, params); } // If the singleton tab isn't already selected, select it. if (params->source_contents != contents_to_navigate_or_insert) { // Use the index before the potential close below, because it could // make the index refer to a different tab. auto gesture_type = user_initiated ? TabStripUserGestureDetails::GestureType::kOther : TabStripUserGestureDetails::GestureType::kNone; bool should_close_this_tab = false; if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) { // Close orphaned NTP (and the like) with no history when the user // switches away from them. if (params->source_contents) { if (params->source_contents->GetController().CanGoBack() || (params->source_contents->GetLastCommittedURL().spec() != chrome::kChromeUINewTabURL && params->source_contents->GetLastCommittedURL().spec() != url::kAboutBlankURL)) { // Blur location bar before state save in ActivateTabAt() below. params->source_contents->Focus(); } else { should_close_this_tab = true; } } } params->browser->tab_strip_model()->ActivateTabAt( singleton_index, TabStripUserGestureDetails(gesture_type)); // Close tab after switch so index remains correct. if (should_close_this_tab) { params->source_contents->Close(); } } } params->navigated_or_inserted_contents = contents_to_navigate_or_insert; // At this point, the `params->navigated_or_inserted_contents` is guaranteed to // be non null, so perform tasks if the navigation has been captured by a web // app, like enqueueing launch params. #if !BUILDFLAG(IS_ANDROID) if (app_navigation) { web_app::NavigationCapturingProcess::AfterWebContentsCreation( std::move(app_navigation), *params->navigated_or_inserted_contents, navigation_handle.get()); } #endif // !BUILDFLAG(IS_ANDROID) return navigation_handle; }
最新发布
08-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值