ChromeDevTools / devtools-protocol

Chrome DevTools Protocol
https://chromedevtools.github.io/devtools-protocol/
BSD 3-Clause "New" or "Revised" License
1.15k stars 226 forks source link

Page.enable hangs for Chrome on Android #180

Closed michaelcypher closed 4 years ago

michaelcypher commented 5 years ago

Steps to reproduce

Chrome Version 75.0.3770.101 (Official Build) (64-bit)
Android 9; Pixel Build/PQ2A.190405.003
Node v11.4.0
puppeteer-core v1.15.0

Starting Chrome on the Android device using adb:

adb shell am start -n com.android.chrome/com.google.android.apps.chrome.Main
adb forward tcp:9222 localabstract:chrome_devtools_remote

Running this code using Node:

const puppeteer = require('puppeteer-core');

(async() => {
  const browser = await puppeteer.connect({
    browserWSEndpoint: 'ws://localhost:9222/devtools/browser',
  });
  const page = await browser.newPage();
  await page.goto('https://example.com');
})();

Current Behavior

When sending the Page.enable event to a Chrome instance running on Android via Puppeteer, the promise hangs and does not resolve. This occurs when executing the browser.newPage() method. When executing this method, an empty new tab is opened on the mobile browser, but the method still hangs.

Note: Target.createTarget does not hang and works as expected. Page.getFrameTree also hangs.

Expected Behavior

Page.enable should not hang.

Thanks.

michaelcypher commented 5 years ago

I've run the same script while connecting to an Android and to a desktop browser and compared the the CDP messages that are sent and received. It looks like the Android browser returns different results to the desktop browser. My best guess is that either the Android or desktop browser is not correctly implementing the protocol (especially with regards to the Targets) function.

>>> indicates an outbound request from my local machine to the browser <<< indicates an inbound request from the browser to my local machine

CDP method requests and responses for Android Chrome:

 android_puppeteer node connect_to_android_browser.js                                 

 >>> { method: 'Target.getBrowserContexts', params: {}, id: 1 }

 <<< {"id":1,"result":{"browserContextIds":[]}}

 >>> { method: 'Target.setDiscoverTargets',
  params: { discover: true },
  id: 2 }

 <<< {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"b2cf09a2-3937-40dc-824c-742d6cc994c3","type":"browser","title":"","url":"","attached":true}}}

 <<< {"id":2,"result":{}}

 >>> { method: 'Target.createTarget',
  params: { url: 'about:blank', browserContextId: undefined },
  id: 3 }

 <<< {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"703A2642305F75EA9960B40A61AE4AC0","type":"page","title":"","url":"","attached":false,"browserContextId":"65CD42E50CA7A32F9166509B325A025E"}}}

 <<< {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"64","type":"page","title":"","url":"about:blank","attached":false}}}

 <<< {"id":3,"result":{"targetId":"64"}}

 >>> { method: 'Target.attachToTarget',
  params: { targetId: '64', flatten: true },
  id: 4 }

 <<< {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"703A2642305F75EA9960B40A61AE4AC0","type":"page","title":"","url":"about:blank","attached":true,"browserContextId":"65CD42E50CA7A32F9166509B325A025E"}}}

 <<< {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"64","type":"page","title":"","url":"about:blank","attached":true}}}

 <<< {"method":"Target.attachedToTarget","params":{"sessionId":"84D62166444AF2687591762176DEA70C","targetInfo":{"targetId":"64","type":"page","title":"","url":"about:blank","attached":true},"waitingForDebugger":false}}

 <<< {"id":4,"result":{"sessionId":"84D62166444AF2687591762176DEA70C"}}

 >>> { sessionId: '84D62166444AF2687591762176DEA70C',
  method: 'Page.enable',
  params: {},
  id: 5 }

 >>> { sessionId: '84D62166444AF2687591762176DEA70C',
  method: 'Page.getFrameTree',
  params: {},
  id: 6 }

 >>> { sessionId: '84D62166444AF2687591762176DEA70C',
  method: 'Target.setAutoAttach',
  params:
   { autoAttach: true,
     waitForDebuggerOnStart: false,
     flatten: true },
  id: 7 }

 >>> { sessionId: '84D62166444AF2687591762176DEA70C',
  method: 'Performance.enable',
  params: {},
  id: 8 }

 >>> { sessionId: '84D62166444AF2687591762176DEA70C',
  method: 'Log.enable',
  params: {},
  id: 9 }

 <<< {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"703A2642305F75EA9960B40A61AE4AC0","type":"page","title":"about:blank","url":"about:blank","attached":true,"browserContextId":"65CD42E50CA7A32F9166509B325A025E"}}}

  (hang)

CDP method requests and responses for Desktop Chrome:

 android_puppeteer node connect_to_desktop_browser.js                                 

 >>> { method: 'Target.getBrowserContexts', params: {}, id: 1 }                                                                                                                          

 <<< {"id":1,"result":{"browserContextIds":[]}}                                                                                                                                          

 >>> { method: 'Target.setDiscoverTargets',                                                                                                                                                                                                                                                                                                                                   
  params: { discover: true },                                                                                                                                                                                                                                                                                                                                             
  id: 2 }                                                                                                                                                                            

 <<< {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"28935A4E1B59610D302EF096FF9008BE","type":"page","title":"New Tab","url":"chrome://newtab/","attached":false,"browserContextId":"54B71F4416FC24B877FE5AD85E34B348"}}}

 <<< {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"6dcd1d00-3aed-47b8-a7df-7ba814e4e7af","type":"browser","title":"","url":"","attached":true}}}

 <<< {"id":2,"result":{}}                                                                                                                                                                

 >>> { method: 'Target.createTarget',                                                                                                                                                                                                                                                                                                                                         
  params: { url: 'about:blank', browserContextId: undefined },                            
  id: 3 }                                                             

 <<< {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"E3791030F6661758C1C0B1C37A1AB747","type":"page","title":"","url":"about:blank","attached":false,"browserContextId":"54B71F4416FC24B877FE5AD85E34B348"}}}                                                                                                                                            

 <<< {"id":3,"result":{"targetId":"E3791030F6661758C1C0B1C37A1AB747"}}

 >>> { method: 'Target.attachToTarget',                                                                                                                                                                                                                                                                                                                                       
  params:                                                                                 
   { targetId: 'E3791030F6661758C1C0B1C37A1AB747', flatten: true },                                                                                                                                                                                                                                                                                                       
  id: 4 }                            

 <<< {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"E3791030F6661758C1C0B1C37A1AB747","type":"page","title":"","url":"about:blank","attached":true,"browserContextId":"54B71F4416FC24B877FE5AD85E34B348"}}}

 <<< {"method":"Target.attachedToTarget","params":{"sessionId":"4EFD88510A8FFC2FBED356CAE8A985CE","targetInfo":{"targetId":"E3791030F6661758C1C0B1C37A1AB747","type":"page","title":"","url":"about:blank","attached":true,"browserContextId":"54B71F4416FC24B877FE5AD85E34B348"},"waitingForDebugger":false}}                                                                

 <<< {"id":4,"result":{"sessionId":"4EFD88510A8FFC2FBED356CAE8A985CE"}}                                                                                                                                                                                                                                                                                                       

 >>> { sessionId: '4EFD88510A8FFC2FBED356CAE8A985CE',                                         
  method: 'Page.enable',                                                                                                                                                                                                                                                                                                                                                  
  params: {}, 
  id: 5 }                                          

 >>> { sessionId: '4EFD88510A8FFC2FBED356CAE8A985CE',
  method: 'Page.getFrameTree',                    
  params: {},                                                                             
  id: 6 }                                                                                                                                                                                                                                                                                                                                                                 

 >>> { sessionId: '4EFD88510A8FFC2FBED356CAE8A985CE',                                                                                                                                    
  method: 'Target.setAutoAttach',                                                                                                                                                                                                                                                                                                                                         
  params:                     
   { autoAttach: true,                           
     waitForDebuggerOnStart: false,               
     flatten: true },                                                                     
  id: 7 }                                         

 >>> { sessionId: '4EFD88510A8FFC2FBED356CAE8A985CE',                                         
  method: 'Performance.enable',                                                                                                                                                                                                                                                                                                                                           
  params: {},                                                                             
  id: 8 }                                                                                                                                                                                                                                                                                                                                                                 

 >>> { sessionId: '4EFD88510A8FFC2FBED356CAE8A985CE',               
  method: 'Log.enable',                            
  params: {},                                                                             
  id: 9 }                                                                                 

 <<< {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"E3791030F6661758C1C0B1C37A1AB747","type":"page","title":"about:blank","url":"about:blank","attached":true,"browserContextId":"54B71F4416FC24B877FE5AD85E34B348"}}}

 <<< {"id":5,"result":{}, "sessionId": "4EFD88510A8FFC2FBED356CAE8A985CE"} 

  ...

Differences:

  1. When creating a new page (using the Target.createTarget method), the desktop browser sends one Target.targetCreated message with a browserContextId and correctly formatted targetId (this is used in the result message), whereas the Android browser sends two Target.targetCreated messages: the first with a browserContextId and a targetId that is formatted similar to the desktop browser message and the second without a browserContextId and an integer targetId that is not formatted similar to the desktop browser message (this is used in the result message.
  2. As a result, Puppeteer connects to the target with the integer targetId (i.e. the target that does not have a corresponding browserContextId) for the Android browser. For the desktop browser, since there is only one created target, it connects to the target with the correctly formatted targetId (which has a corresponding browserContextId).
  3. For the Android browser, both created targets have the info changed to reflect that they have been attached too once Puppeteer sends the Target.attachToTarget method. For the desktop browser, the single created target has the info change to reflect that is has been attached to.

My best guess is that the Android browser is incorrectly returning two targets when Puppeteer sends a message to create a target. The latter targetId is incorrectly used as a result. Hope this helps and hope this indicates that there is a bug on Android Chrome (or at least a difference between Android Chrome and desktop Chrome).

michaelcypher commented 5 years ago

I confirmed that the issue is due to the created target id (that is returned when puppeteer sends a Target.createTarget method to Android Chrome) not having a corresponding browserContextId. For example, if I use an already existing target with a browserContextId, I can access the page just fine. This obviously only works if there is already an existing page in the browser, so this workaround is not permanent.

const puppeteer = require('puppeteer-core');

(async() => {
  // Connect to Android Chrome browser
  const browser = await puppeteer.connect({
    browserWSEndpoint: 'ws://localhost:9222/devtools/browser',
  });

  // If a page already exists (with a browser context id), use it
  const targets = await browser.targets().filter(
    target => target._targetInfo.browserContextId != null);

  // We don't have a way of creating a new target for Android Chrome due to the
  // bug in https://github.com/ChromeDevTools/devtools-protocol/issues/180.
  if (targets.length === 0) {
    throw Error('no target exists with a browser context id');
  }

  // Use the first existing page
  const target = targets[0];
  const page = await target.page();

  await page.goto('https://cypher.codes');
})();
TimvdLippe commented 4 years ago

This repository is related to Chrome DevTools Protocol, but does not track issues regarding its definition or implementation. If you want to file an issue for the Chrome DevTools Protocol, please open an issue on https://crbug.com under component: Platform>DevTools>Platform. Thanks in advance!