From 782c97ade53b663a5b2b46aaa4d0cc381e51c457 Mon Sep 17 00:00:00 2001 From: udit3333 Date: Thu, 23 Jan 2020 09:27:12 -0800 Subject: [PATCH] Add Svg preview handler (#1129) * Added a new project for Svg preview handler * Added initial implementation of Svg Preview Handler * Fixed output path * Added Unit Test Project * Added StreamWrapper and Update Svg Control * Updated Svg Handler Guid * Removed migration backup folder * Removed Fluent Assertions NuGet * Added Comments for StreamWrapper * Removed the manual GC collect * Added unit tests for Svg preview Handler * Updated the xml doc for stream wrapper --- .../Properties/AssemblyInfo.cs | 40 +++ .../SvgPreviewHandler/SvgPreviewControl.cs | 55 ++++ .../SvgPreviewHandler/SvgPreviewHandler.cs | 36 +++ .../SvgPreviewHandler.csproj | 85 ++++++ .../SvgPreviewHandler/SvgPreviewHandler.snk | Bin 0 -> 596 bytes .../Properties/AssemblyInfo.cs | 20 ++ .../SvgPreviewControlTests.cs | 101 ++++++++ .../UnitTests-SvgPreviewHandler.csproj | 75 ++++++ .../common/Utilities/StreamWrapper.cs | 242 ++++++++++++++++++ src/modules/previewpane/common/common.csproj | 11 +- ...andler.cs => StreamBasedPreviewHandler.cs} | 2 +- .../previewpane/common/packages.config | 4 - 12 files changed, 662 insertions(+), 9 deletions(-) create mode 100644 src/modules/previewpane/SvgPreviewHandler/Properties/AssemblyInfo.cs create mode 100644 src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs create mode 100644 src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs create mode 100644 src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj create mode 100644 src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.snk create mode 100644 src/modules/previewpane/UnitTests-SvgPreviewHandler/Properties/AssemblyInfo.cs create mode 100644 src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs create mode 100644 src/modules/previewpane/UnitTests-SvgPreviewHandler/UnitTests-SvgPreviewHandler.csproj create mode 100644 src/modules/previewpane/common/Utilities/StreamWrapper.cs rename src/modules/previewpane/common/handlers/{SteamBasedPreviewHandler.cs => StreamBasedPreviewHandler.cs} (86%) delete mode 100644 src/modules/previewpane/common/packages.config diff --git a/src/modules/previewpane/SvgPreviewHandler/Properties/AssemblyInfo.cs b/src/modules/previewpane/SvgPreviewHandler/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..0bc87e35c8e --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SvgPreviewHandler")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SvgPreviewHandler")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("da425894-6e13-404f-8dcb-78584ec0557a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs new file mode 100644 index 00000000000..c41c48e7fcc --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; +using Common; +using Common.Utilities; + +namespace SvgPreviewHandler +{ + /// + /// Implementation of Control for Svg Preview Handler. + /// + public class SvgPreviewControl : FormHandlerControl + { + private Stream dataSourceStream; + + /// + /// Start the preview on the Control. + /// + /// Stream reference to access source file. + public override void DoPreview(T dataSource) + { + this.InvokeOnControlThread(() => + { + WebBrowser browser = new WebBrowser(); + this.dataSourceStream = new StreamWrapper(dataSource as IStream); + + browser.DocumentStream = this.dataSourceStream; + browser.Dock = DockStyle.Fill; + browser.IsWebBrowserContextMenuEnabled = false; + browser.ScriptErrorsSuppressed = true; + browser.ScrollBarsEnabled = true; + this.Controls.Add(browser); + base.DoPreview(dataSource); + }); + } + + /// + /// Free resources on the unload of Preview. + /// + public override void Unload() + { + base.Unload(); + if (this.dataSourceStream != null) + { + this.dataSourceStream.Dispose(); + this.dataSourceStream = null; + } + } + } +} diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs new file mode 100644 index 00000000000..642cd13e83f --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using Common; + +namespace SvgPreviewHandler +{ + /// + /// Extends for Svg Preview Handler. + /// + [PreviewHandler("SvgPreviewHandler", ".svg", "{88235ab2-bfce-4be8-9ed0-0408cd8da792}")] + [ProgId("SvgPreviewHandler")] + [Guid("ddee2b8a-6807-48a6-bb20-2338174ff779")] + [ClassInterface(ClassInterfaceType.None)] + [ComVisible(true)] + public class SvgPreviewHandler : StreamBasedPreviewHandler + { + private SvgPreviewControl svgPreviewControl; + + /// + public override void DoPreview() + { + this.svgPreviewControl.DoPreview(this.Stream); + } + + /// + protected override IPreviewHandlerControl CreatePreviewHandlerControl() + { + this.svgPreviewControl = new SvgPreviewControl(); + return this.svgPreviewControl; + } + } +} diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj new file mode 100644 index 00000000000..6963dcf1002 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj @@ -0,0 +1,85 @@ + + + + + Debug + AnyCPU + {DA425894-6E13-404F-8DCB-78584EC0557A} + Library + Properties + SvgPreviewHandler + SvgPreviewHandler + v4.8 + 512 + true + + + true + + + SvgPreviewHandler.snk + + + true + bin\Debug\ + DEBUG;TRACE + bin\Debug\SvgPreviewHandler.xml + 2 + true + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + bin\Release\ + TRACE + bin\Release\SvgPreviewHandler.xml + true + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + + + + Form + + + + + + + {af2349b8-e5b6-4004-9502-687c1c7730b1} + Common + + + + + StyleCop.json + + + + + + 1.1.118 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.snk b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.snk new file mode 100644 index 0000000000000000000000000000000000000000..3375897afa1bf7275e1305344e104c29839d1a18 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096Ay18EQ#E7hr6A49{)c7sb#Wc%soHgfg zLl?)ROEo}E*gqsDw3>`o6XUv6bKvqtPSL?8K0$N!WTZ4Q4^Mj`pIn8 zszE{v&ggSHEBv7I(A1)&q{HnT#o(BO7|M;3h`@e)EQBtsRgNB=_{(er|8ytqLeQTNv9S(AV|(); + + // Act + svgPreviewControl.DoPreview(mockStream.Object); + + // Assert + Assert.AreEqual(svgPreviewControl.Controls.Count, 1); + Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowser)); + } + + [TestMethod] + public void SvgPreviewControl_ShouldSetDocumentStream_WhenDoPreviewCalled() + { + // Arrange + var svgPreviewControl = new SvgPreviewControl(); + var mockStream = new Mock(); + + // Act + svgPreviewControl.DoPreview(mockStream.Object); + + // Assert + Assert.IsNotNull(((WebBrowser)svgPreviewControl.Controls[0]).DocumentStream); + } + + [TestMethod] + public void SvgPreviewControl_ShouldDisableWebBrowserContextMenu_WhenDoPreviewCalled() + { + // Arrange + var svgPreviewControl = new SvgPreviewControl(); + var mockStream = new Mock(); + + // Act + svgPreviewControl.DoPreview(mockStream.Object); + + // Assert + Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).IsWebBrowserContextMenuEnabled, false); + } + + [TestMethod] + public void SvgPreviewControl_ShouldFillDockForWebBrowser_WhenDoPreviewCalled() + { + // Arrange + var svgPreviewControl = new SvgPreviewControl(); + var mockStream = new Mock(); + + // Act + svgPreviewControl.DoPreview(mockStream.Object); + + // Assert + Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).Dock, DockStyle.Fill); + } + + [TestMethod] + public void SvgPreviewControl_ShouldSetScriptErrorsSuppressedProperty_WhenDoPreviewCalled() + { + // Arrange + var svgPreviewControl = new SvgPreviewControl(); + var mockStream = new Mock(); + + // Act + svgPreviewControl.DoPreview(mockStream.Object); + + // Assert + Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScriptErrorsSuppressed, true); + } + + [TestMethod] + public void SvgPreviewControl_ShouldSetScrollBarsEnabledProperty_WhenDoPreviewCalled() + { + // Arrange + var svgPreviewControl = new SvgPreviewControl(); + var mockStream = new Mock(); + + // Act + svgPreviewControl.DoPreview(mockStream.Object); + + // Assert + Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScrollBarsEnabled, true); + } + } +} diff --git a/src/modules/previewpane/UnitTests-SvgPreviewHandler/UnitTests-SvgPreviewHandler.csproj b/src/modules/previewpane/UnitTests-SvgPreviewHandler/UnitTests-SvgPreviewHandler.csproj new file mode 100644 index 00000000000..5c99aca7f70 --- /dev/null +++ b/src/modules/previewpane/UnitTests-SvgPreviewHandler/UnitTests-SvgPreviewHandler.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {060D75DA-2D1C-48E6-A4A1-6F0718B64661} + Library + Properties + UnitTests_SvgPreviewHandler + UnitTests-SvgPreviewHandler + v4.8 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x64 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + x64 + + + + + + + + + + + + + 4.13.1 + + + 1.3.2 + + + 1.3.2 + + + + + {AF2349B8-E5B6-4004-9502-687C1C7730B1} + Common + + + {DA425894-6E13-404F-8DCB-78584EC0557A} + SvgPreviewHandler + False + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/common/Utilities/StreamWrapper.cs b/src/modules/previewpane/common/Utilities/StreamWrapper.cs new file mode 100644 index 00000000000..8d31b9eff7d --- /dev/null +++ b/src/modules/previewpane/common/Utilities/StreamWrapper.cs @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Common.Utilities +{ + /// + /// Wraps interface into Class. + /// + /// + /// Implements only read from the stream functionality. + /// + public class StreamWrapper : Stream + { + private IStream stream; + + /// + /// Initializes a new instance of the class. + /// + /// A pointer to an interface that represents the stream source. + public StreamWrapper(IStream stream) + { + this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead + { + get + { + return true; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get + { + return true; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get + { + this.CheckDisposed(); + System.Runtime.InteropServices.ComTypes.STATSTG stat; + + // Stat called with STATFLAG_NONAME. The pwcsName is not required more details https://docs.microsoft.com/en-us/windows/win32/api/wtypes/ne-wtypes-statflag + this.stream.Stat(out stat, 1); // STATFLAG_NONAME + + return stat.cbSize; + } + } + + /// + /// Gets or Sets the position within the current. + /// + public override long Position + { + get + { + return this.Seek(0, SeekOrigin.Current); + } + + set + { + this.Seek(value, SeekOrigin.Begin); + } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the stream has been reached. + public override int Read(byte[] buffer, int offset, int count) + { + this.CheckDisposed(); + + if (offset < 0 || count < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } + + byte[] localBuffer = buffer; + + if (offset > 0) + { + localBuffer = new byte[count]; + } + + IntPtr bytesReadPtr = Marshal.AllocCoTaskMem(sizeof(int)); + + try + { + this.stream.Read(localBuffer, count, bytesReadPtr); + int bytesRead = Marshal.ReadInt32(bytesReadPtr); + + if (offset > 0) + { + Array.Copy(localBuffer, 0, buffer, offset, bytesRead); + } + + return bytesRead; + } + finally + { + Marshal.FreeCoTaskMem(bytesReadPtr); + } + } + + /// + /// Sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type System.IO.SeekOrigin indicating the reference point used to obtain the new position. + /// The new position within the current stream. + public override long Seek(long offset, SeekOrigin origin) + { + this.CheckDisposed(); + int dwOrigin; + + // Maps the SeekOrigin with dworigin more details: https://docs.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-stream_seek + switch (origin) + { + case SeekOrigin.Begin: + dwOrigin = 0; // STREAM_SEEK_SET + break; + + case SeekOrigin.Current: + dwOrigin = 1; // STREAM_SEEK_CUR + break; + + case SeekOrigin.End: + dwOrigin = 2; // STREAM_SEEK_END + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + IntPtr posPtr = Marshal.AllocCoTaskMem(sizeof(long)); + + try + { + this.stream.Seek(offset, dwOrigin, posPtr); + return Marshal.ReadInt64(posPtr); + } + finally + { + Marshal.FreeCoTaskMem(posPtr); + } + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + /// + /// Not implemented current implementation supports only read. + /// + public override void Flush() + { + throw new NotImplementedException(); + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// /// + /// Not implemented current implementation supports only read. + /// + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// + /// Not implemented current implementation supports only read. + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.stream != null) + { + if (Marshal.IsComObject(this.stream)) + { + Marshal.ReleaseComObject(this.stream); + } + + this.stream = null; + } + } + + private void CheckDisposed() + { + if (this.stream == null) + { + throw new ObjectDisposedException(nameof(StreamWrapper)); + } + } + } +} diff --git a/src/modules/previewpane/common/common.csproj b/src/modules/previewpane/common/common.csproj index f3763f5953d..7d9573486a1 100644 --- a/src/modules/previewpane/common/common.csproj +++ b/src/modules/previewpane/common/common.csproj @@ -77,19 +77,22 @@ - + + StyleCop.json - - - + + 1.1.118 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + \ No newline at end of file diff --git a/src/modules/previewpane/common/handlers/SteamBasedPreviewHandler.cs b/src/modules/previewpane/common/handlers/StreamBasedPreviewHandler.cs similarity index 86% rename from src/modules/previewpane/common/handlers/SteamBasedPreviewHandler.cs rename to src/modules/previewpane/common/handlers/StreamBasedPreviewHandler.cs index d329a5fadc1..8412dd7b92a 100644 --- a/src/modules/previewpane/common/handlers/SteamBasedPreviewHandler.cs +++ b/src/modules/previewpane/common/handlers/StreamBasedPreviewHandler.cs @@ -10,7 +10,7 @@ namespace Common /// /// Extends the by implementing IInitializeWithStream. /// - public abstract class SteamBasedPreviewHandler : PreviewHandlerBase, IInitializeWithStream + public abstract class StreamBasedPreviewHandler : PreviewHandlerBase, IInitializeWithStream { /// /// Gets the stream object to access file. diff --git a/src/modules/previewpane/common/packages.config b/src/modules/previewpane/common/packages.config deleted file mode 100644 index e18dca45cda..00000000000 --- a/src/modules/previewpane/common/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file