MasterDevs / ChromeDevTools

.NET library to interact with the Chrome Debugger
MIT License
135 stars 90 forks source link

PrintToPDFCommand hangs forever when printing to PDF #37

Open ststeiger opened 5 years ago

ststeiger commented 5 years ago

PrintToPDFCommand hangs forever when printing to PDF

public static double cm2inch(double centimeters)
{
    return centimeters * 0.393701;
}

PrintToPDFCommand printCommand = new PrintToPDFCommand()
{
    MarginTop = 0,
    MarginLeft = 0,
    MarginRight = 0,
    MarginBottom = 0,
    PrintBackground = true,
    Landscape = false,
    PaperWidth = cm2inch(21),
    PaperHeight = cm2inch(29.7)
};

System.Console.WriteLine("Printing PDF");
CommandResponse<PrintToPDFCommandResponse> pdf = await chromeSession.SendAsync(printCommand);
System.Console.WriteLine("PDF printed.");

byte[] pdfData = System.Convert.FromBase64String(pdf.Result.Data);
System.IO.File.WriteAllBytes("output.pdf", pdfData);
System.Console.WriteLine("PDF stored");
ststeiger commented 5 years ago

Found the bug: If scale is not present, then it throws an error, but because it's in task.run, it's not reported. ==> the await never returns... Setting Scale = x is mandatory.

scale is outside [0.1 - 2] range

bug here:

        private Task<ICommandResponse> SendCommand(Command command, CancellationToken cancellationToken)
        {
            var settings = new JsonSerializerSettings
            {
                ContractResolver = new MessageContractResolver(),
                NullValueHandling = NullValueHandling.Ignore,
            };
            var requestString = JsonConvert.SerializeObject(command, settings);
            var requestResetEvent = new ManualResetEventSlim(false);
            _requestWaitHandles.AddOrUpdate(command.Id, requestResetEvent, (id, r) => requestResetEvent);
            return Task2.Run(() =>
            {
                EnsureInit();
                _webSocket.Send(requestString);
                requestResetEvent.Wait(cancellationToken);
                ICommandResponse response = null;
                _responses.TryRemove(command.Id, out response);
                _requestWaitHandles.TryRemove(command.Id, out requestResetEvent);
                return response;
            });
        }
ststeiger commented 5 years ago

OK, found the bug. In MasterDev.ChromeDevTools\ChromeSession.cs you do task.ContinueWith( t => tcs.SetResult((TDerived)t.Result)) However, if it returns an error, (TDerived)t.Result yields an exception, and the await never returns. It looks like you need to catch the errors there:

        private Task<TDerived> CastTaskResult<TBase, TDerived>(Task<TBase> task) where TDerived: TBase
        {
            var tcs = new TaskCompletionSource<TDerived>();
            task.ContinueWith(
                // t => tcs.SetResult((TDerived)t.Result)
                delegate (Task<TBase> t) {
                    try
                    {
                        TDerived res = (TDerived)t.Result;
                        tcs.SetResult(res);
                    }
                    catch (System.Exception ex)
                    {
                        tcs.SetException(ex);
                    }
                }, TaskContinuationOptions.OnlyOnRanToCompletion
            );

            task.ContinueWith(
                    // t => tcs.SetException(t.Exception.InnerExceptions)
                    delegate (Task<TBase> t) {
                        try
                        {
                            tcs.SetException(t.Exception.InnerExceptions);
                        }
                        catch (System.Exception ex)
                        {
                            tcs.SetException(ex);
                        }

                    }
                    ,TaskContinuationOptions.OnlyOnFaulted
            );

            task.ContinueWith(
                    // t => tcs.SetCanceled()
                    delegate (Task<TBase> t)
                    {
                        try
                        {
                            tcs.SetCanceled();
                        }
                        catch (System.Exception ex)
                        {
                            tcs.SetException(ex);
                        }
                    }
                , TaskContinuationOptions.OnlyOnCanceled
            );

            return tcs.Task;
        }
ststeiger commented 5 years ago

And it might be better to output the actual error:

private Task<TDerived> CastTaskResult<TBase, TDerived>(Task<TBase> task) where TDerived: TBase
{
    TaskCompletionSource<TDerived> tcs = new TaskCompletionSource<TDerived>();
    task.ContinueWith(
        // t => tcs.SetResult((TDerived)t.Result)
        delegate (Task<TBase> t) 
        {
            try
            {
                TDerived res = (TDerived)t.Result;
                tcs.SetResult(res);
            }
            catch (System.Exception ex)
            {
                ErrorResponse error = t.Result as ErrorResponse;

                if (error != null)
                {
                    string errorMessage = "";

                    try
                    {
                        errorMessage += "Id " + System.Convert.ToString(error.Id, System.Globalization.CultureInfo.InvariantCulture) + System.Environment.NewLine;
                    }
                    catch
                    { }

                    try
                    {
                        errorMessage += "Error " + System.Convert.ToString(error.Error.Code, System.Globalization.CultureInfo.InvariantCulture) + System.Environment.NewLine;
                    }
                    catch
                    { }

                    try
                    {
                        errorMessage += error.Error.Message + System.Environment.NewLine;
                    }
                    catch
                    { }

                    try
                    {
                        errorMessage += error.Method;
                    }
                    catch
                    { }

                    tcs.SetException( new Exception(errorMessage, ex) );
                }
                else
                    tcs.SetException(ex);
            }
        }, TaskContinuationOptions.OnlyOnRanToCompletion
    );

    task.ContinueWith(
            // t => tcs.SetException(t.Exception.InnerExceptions)
            delegate (Task<TBase> t) 
            {
                try
                {
                    tcs.SetException(t.Exception.InnerExceptions);
                }
                catch (System.Exception ex)
                {
                    tcs.SetException(ex);
                }

            }
            ,TaskContinuationOptions.OnlyOnFaulted
    );

    task.ContinueWith(
            // t => tcs.SetCanceled()
            delegate (Task<TBase> t)
            {
                try
                {
                    tcs.SetCanceled();
                }
                catch (System.Exception ex)
                {
                    tcs.SetException(ex);
                }
            }
        , TaskContinuationOptions.OnlyOnCanceled
    );

    return tcs.Task;
}