Using external JavaScript files in a WinForms/WPF WebBrowser control - javascript
I found a cool feature on a website, implemented in JavaScript, I'd like to use it as is in my desktop application (for personal use).
During my experiments I managed to generate custom HTML on the fly, feed it to the browser using webBrowser1.DocumentText = [my generated HTML]
I've managed to put some inline JavaScript into the HTML, and hook it up via a ScriptManager so that I can call the JavaScript from my C# code, pass a value to it, and get a return value.
But the feature I'm trying to use is a bit more complicated: it's no less than 10 JavaScript files. 2 of them are referenced directly in the web page the usual way <script src="/js/script1.js" type="text/javascript"></script>
The other 8 are loaded in one of the scripts:
var elem = document.createElement("script");
elem.type = "text/javascript";
elem.src = "/js/" + filename;
document.body.appendChild(elem);
These 8 files are in fact data files, even though the data is represented in JavaScript. They're pretty large, over 1MB each. Stuffing it all into the HTML file seems quite stupid. Also, the script that loads the data creates a "file map" and further refers to the data based on which file it's in:
var fileMap = [
[/[\u0020-\u00ff]/, 'file1.js'],
[/[\u3000-\u30ff]/, 'file2.js'],
[/[\u4e00-\u5dff]/, 'file3.js'],
...
I don't want to resort to modifying the JavaScript, because it's not exactly my strong point. So the browser needs to "see" the js files in order to be able to use them. I thought of creating the file structure locally, and navigating the browser there. But I don't want any loose files in my solution. I'd like to have everything embedded if possible. And I doubt I can get the browser to navigate to an embedded resource, and see other embedded resources as files. Any idea how I could get around this?
EDIT:
I've tried to do it with local files. No luck. I get the HTML to load properly, but when I try to invoke a JavaScript call, nothing happens. I tried pointing the browser to those js files, to make sure they're there. They are. I tried an element with src attribute pointing to an image in the same subfolder as the script files. It gets rendered. It's as if external js files refuse to load.
I had a similar need as your scenario and I addressed it using two key points embedded in two other Stack Overflow answers. As noted by SLaks' answer here the first key is using the syntax file:/// as the prefix for an absolute path to external files. The second is using .Replace("\\", "/") for an absolute file path as listed in Adam Plocher's answer and one of his follow-up comments here.
In short, the final output for each external file in an HTML page will look something like:
<link href="file:///c:/users/david/myApp/styles/site.css" rel="stylesheet" type="text/css">
or
<script src="file:///c:/users/david/myApp/scripts/JavaScript1.js"></script>
Using the format in the samples above in my HTML file resulted in the WebBrowser control loading external CSS, image or script files.
The details and solving the scenario in the question
In the womd's answer in the first referenced SO answer above he used the method System.IO.File.ReadAllText() to load script files and embedded the text of the script files into the <head> tag. As you indicated in your question loading script files directly into the HTML page is not what you're looking to do.
The solution below involves using the same System.IO.File.ReadAllText() method but loads the text of the HTML page instead. The premise works similar to the Razor View Engine in ASP.NET.
The main idea in the solution below involves adding a temporary string in an HTML page that will be loaded into the WebBrowser control and then replacing this temporary string in a C# method in my app just before the HTML page is set to be loaded into the WebBrowser control.
Here are the basic steps to my solution:
Add a temporary string for each external reference in the HTML file.
Declare a variable for the absolute path in a script tag within the HTML file. This step is not necessary unless you're going to use the absolute path elsewhere within your JavaScript code. Your scenario involves delay loading external script files via JavaScript code so this step was necessary.
Modify the src property in the JavaScript code that delay loads the other script files with the absolute path variable.
Add a method in your app to loads the HTML page file as a text string and then replaces all temporary string instances with an absolute path containing the prefix 'file:///'. The absolute path should have forward slashes.
Set the 'DocumentText' property on the WebBrowser control to the updated HTML.
Set the 'Copy to Output Directory' of each external file in your project to 'Copy always' or 'Copy if newer'. This step may not be necessary if you have a fixed location to your external files and that location is not within the build or publish directory used by Visual Studio.
The following are the details for each step. I added a lot of detail that you can skip. I was verbose to reduce any confusion since the steps make changes to several places in the project.
1. Using a temporary string
I used the string "/ReplaceWithAbsolutePath/" but you can use any distinct text. Each reference to an external file in the HTML page looks like:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<link href="/ReplaceWithAbsolutePath/styles/site.css" rel="stylesheet" type="text/css">
<script type="text/javascript">
var absolutePath = "/ReplaceWithAbsolutePath/";
</script>
</head>
<body>
<p>My web page</p>
<script src="/ReplaceWithAbsolutePath/scripts/JavaScript1.js"></script>
</body>
</html>
2. Declare absolute path variable
Note in the above HTML page I listed a <script> tag with the declared variable 'absolutePath' set to the temporary string. (In the HTML page above the variable is added a global variable and that is not necessarily best practice. You can declare the variable within a namespace instead of declaring it in the global namespace.)
3. Modify the delay load script to include absolute path variable
Add the 'absolutePath' variable to your JavaScript file that delay loads other JavaScript files containing your data.
elem.src = absolutePath + "/js/" + filename;
4. C# method to replace all temporary string instances
Within your project add the following line to your form load event handler or place this line somewhere in your initialization of the WebBrowser control.
webBrowser1.DocumentText = GetUpdatedHtmlWithAbsolutePaths("/ReplaceWithAbsolutePath/", "HTMLPage1.html");
Add the following method to your code. Update the call to the method in the line above with the name of the class instance where the following method is placed.
// The result of this method will look like the following example:
// <script src="file:///c:/users/david/documents/myApp/scripts/JavaScript1.js"></script>
public string GetUpdatedHtmlWithAbsolutePaths(string tempPathString, string htmlFilename)
{
// Get the directory as the application
// stackoverflow.com/questions/674857/should-i-use-appdomain-currentdomain-basedirectory-or-system-environment-current
// Note that the 'BaseDirectory' property will return a string with trailing backslashes ('\\')
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
// Replace '//' with '/' in the appDirectory string
appDirectory = appDirectory.Replace("\\", "/");
// Read all of the HTML text from the HTML page file
string html = System.IO.File.ReadAllText(appDirectory + #"\" + htmlFilename);
// Replace all '/ReplaceWithAbsolutePath/' strings within the HTML text with
// the absolute path on the local machine
html = html.Replace(tempPathString, "file:///" + appDirectory);
return html;
}
5. Set the DocumentText property of the WebBrowser control
I added the initialization of the WebBrowser control in the form load event handler but you can, of course, add the line that sets the DocumentText property wherever you initialize your WebBrowser control.
private void Form1_Load(object sender, EventArgs e)
{
// Set the document text of the web browser control with the updated HTML
webBrowser1.DocumentText = GetUpdatedHtmlWithAbsolutePaths("HTMLPage1.html");
}
6. Set the 'Copy to Output Directory' of each external file
Take a look at the answer posted by Matthew Watson in this Stack Overflow question if you want your external files included in your solution/project file structure.
You can add files to your project and select their properties: "Build
Action" as "Content" and "Copy to output directory" as "Copy Always"
or Copy if Newer (the latter is preferable because otherwise the
project rebuilds fully every time you build it).
Then those files will be copied to your output folder.
This is better than using a post build step because Visual Studio will
know that the files are part of the project. (That affects things like
ClickOnce applications which need to know what files to add to the
clickonce data.)
In short, add the external file to your project. You can add the external to any subfolder in your project. (In Visual Studio 2013 or 2015 -- I don't have VS2012) Right-click on the external file in the Solution Explorer and select Properties from the context menu. The Properties pane will be displayed. In the Properties pane change the setting for 'Copy to Output Directory' to 'Copy always' or 'Copy if newer'.
Use View Source to verify absolute path strings
Run your project and it should load your external files in the WebBrowser control. Assuming you have not set the property wbChartContainer.IsWebBrowserContextMenuEnabled = false; in code or in the Properties pane for WebBrowser control you can right-click on the WebBrowser control when your form is running. Click 'View Source' from the context menu and check the paths to your external resources in the View Source window.
Related
collect all the js css and img resources used in a html file
I want to write a npm package to localize an html url. 1. using the html url download the html page 2. parse the html file, extract all the js, css and img files used in the html and local these resources. 3. If these js, css and img files using some external resources, localize these resources. For example, extract background image in the css. The first and second requirements are easy to meet. But I have no idea about the last one. I can parse the all the css files and localize the resources used in it. But how can I parse the js files? For example: If the js adds a 'script src = XXX' tag into the html dom, how can I extract the src?
I think I would try to use a headless browser to catch every network calls instead of trying to parse the code. I didn't used it personally but PhantomJS seems to fit the bill. It can be used to load a webpage then execute any script / css that would normally happen on the request and execute stuff once the page is loaded. The network monitoring features are probably what you'll want to use.
IE extension - Injecting Javascript file
I am developing an IE extension which works on sites opened in Internet Explorer. It is designed to work the same way as a chrome extension. I am trying to implement the Background function of chrome extension using c++ and the content script by injecting JS into the current web page. The content script, I am trying to load via IHTMLWindow2 execScript on Document load event. Now that I need to inject JS files directly I tried the following. Had the JS file under a folder inside the Project destination and tried to inject using physical path. std::wstring filePath(_T("d:/xx/xxx/x/x/Content/myFile.js")); scriptText = scriptText+ filePath + endScript; VARIANT vrt = {0}; HRESULT hrexec = ifWnd->execScript(SysAllocString(scriptText.c_str()),L"javascript", &vrt); The scriptText has some javascript code to create script element with type and src attributes. The filePath holds the physical path towards the js file.[Also tried relative path but it was a no go] The above was not working correctly in IE9 due to mixed content issue, upon which I researched to figure out that IE9 expects the js file to be retrieved from a server rather than local physical path. The console throws me the below exception. SEC7111: HTTPS security is compromised by file:<filepath> SCRIPT16388: Operation aborted I am pretty much not sure is there any round about for injecting Javascript to the current DOM from the physical path. Please help me on this. Also let me know is there any other possibility of injecting the JS file from the current working directory into the DOM.
You don't have to inject a <SCRIPT> tag in the DOM. If your js file contains: var strHello = "Hello"; function SayHello() { alert( strHello ); } you may just read the file into memory, construct a BSTR string with it, and pass that string to IHTMLWindow2::execScript. Later, another call to execScript with the string SayHello(); will popup the alert box. The code you injected is still here.
Call javascript code on page using PageAsyncTask
I have two ASPX pages (P1.aspx and P2.aspx). The first (P1.aspx) contains a lot of JavaScript code. How can I call all this JavaScript from another page (P2.aspx)? I tried to do this using PageAsyncTask from code behind of P2.aspx, but JavaScript code (on P1.aspx) didn't work. Any suggestions?
you cant do that. put the javascript into JS file and reference it when needed. What you can do ( I think) is to get the HTML content of the file and then EXTRACT the JS data edit try that (I dont think that it will include the inside JS - but try it yourself) WebRequest oRequest; WebResponse oResponse; oRequest = WebRequest.Create("http://www.google.com/"); oResponse = oRequest.GetResponse(); StreamReader sr = new StreamReader(oResponse.GetResponseStream()); string pagedata = sr.ReadToEnd(); pagedata+=#"sdfsdf";
All javascript code you want to use on a page has to be included in that page or dynamically loaded by that page. You cannot call code that is only in another page. The usual way of sharing code among pages is to have a .js file that contains common code that is included in more than one page and then a .js file that contains code that is unique to each specific page (if required).
One way to do this is <!--#INCLUDE FILE="somefile.aspx" --> but your aspx page will complain that there can be only one 'page' directive. So in order to do this properly you need to include as the previous mentiond the js files in the aspx file. One way is makeing a master page and include all the necessary js code in there, then all pages that are loaded within the masterpage will automatically inherit the javascript included libraries. Another way is to make an html file that includes all the libraries and then use the <!--#INCLUDE FILE="myjslibs.html" --> to include all your code there to each page. So actually copy all your code in one file and then include that just one file each time in every page.
Edit external JavaScript file after breakpoint is hit
In the VS2010 IDE when a breakpoint (or an error) is hit, it opens a read-only [dynamic] version of the external JavaScript file I referenced. My workflow would be vastly improved if I could immediately edit this file, and refresh the browser. That is as opposed to digging up the original JS file opening it, finding the correct line and editing there. I only know that this is possible because I was able to do this on my old work computer configuration, but for the life of me I can't duplicate it at home. Has anyone made this work? Perhaps an extension? or maybe it has to with the way the files are referenced, or my basehref tag, or url rewriting.
This happens when the base href specifies a domain other than localhost. My issue was that to enable a local environment for Facebook JS, I need my domain in the url. So I set up my host file to remap localhost.mydomain.com to localhost. When the Visual Studio IDE encounters a file reference which is something other than localhost, it does not attempt to grab the local file since it assumes (correctly in most cases) that it is being served from another site. In these cases it loads a file as [dynamic] and readonly. Here is the test case: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <base href="http://localhost.mydomain.com/virtual-directory/" /> <script type="text/javascript" src="test.js"></script> </head> <body> </html> Any breakpoint within test.js will result in opening a readonly dynamic file.
how are you referencing your files? whenever a script block is written inside the html or is dynamically inserted the debugger will open the instance of the page where the code stops. If you reference the script using tags vs should open the original script file (at least that's what it does on my machine). could you upload an example of your current structure?
Javascript read files in folder
I have the following problem which I'm trying to solve with javascript. I have a div with a background image specified in a css file, and I want my javascript to change that image periodically (let`s say every 5 secs). I know how to do that, the problem is that I have a folder of images to choose from for the back image. I need to be able to read the filenames (from the image folder) into an array, change the background image of the div, delay for 5 seconds and change again.
in your javascript, use an array like var images = [ "image1.jpg", "image2.jpg", "image3.jpg" ]; function changeImage() { var image = document.getElementById("yourimage"); image.src=$images[changeImage.imageNumber]; changeImage.imageNumber = ++changeImage.imageNumber % images.length; } changeImage.imageNumber=0; setInterval(changeImage,5000); The values in the array should be generated by your php
You're still going to need php or asp to query the folder for files. Javascript will not be able to "remotely" inspect the file system. You can do something like the following in jQuery: $.ajax({ url: 'getFolderAsArrayOfNames.php', dataType: 'json', success: function(data) { for(var i=0;i<data.length;i++) { // do what you need to do } }); }); And in your getFolderAsArrayOfNames.php, something like this: echo "function " .$_GET['callback'] ."() {return " .json_encode(scandir('somepath/*.jpg')) ."}";
If you are using Apache as your web server, and if you can configure it to provide a default directory listing for your images folder (use the appropriate options in httpd.conf and/or .htaccess), and if you don't care that the list of images is available to everyone who visits your web site, then you don't need PHP or any other server-side processing. You can use XMLHttpRequest (or the jQuery ajax function, which is a nice wrapper) to get the listing for the folder. The response will be HTML and it will look something like this: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <html> <head> <title>Index of /demo1/images</title> </head> <body> <h1>Index of /demo1/images</h1> <pre><img src="/icons/blank.gif" alt="Icon "> Name Last modified Size Description<hr><img src="/icons/back.gif" alt="[DIR]"> Parent Directory - <img src="/icons/image2.gif" alt="[IMG]"> tree.gif 17-Mar-2009 12:58 6.2K <img src="/icons/image2.gif" alt="[IMG]"> house.gif 17-Mar-2009 12:58 6.5K <img src="/icons/image2.gif" alt="[IMG]"> car.gif 02-Mar-2009 15:37 8.4K <img src="/icons/image2.gif" alt="[IMG]"> elephant.jpg 02-Mar-2009 15:37 3.4K <hr></pre> <address>Apache/2.0.63 (Unix) Server at zeppo Port 80</address> </body></html> Since this output is pretty predictable, you might try parsing out the filenames using a JavaScript regular expression, but it's probably just as easy and more robust to create a hidden DIV on your page, put the HTML response into that DIV, and then use DOM methods to find <a href>s that are after <img> tags with an alt="[IMG]" attribute. Once again, using jQuery Selectors or similar helper methods available in other toolkits will make this DOM parsing pretty easy. Once you have the URL of the new image (parsed from the href), you can set the new CSS background for your div with the .style.backgroundImage property.
You cannot do any file IO using JavaScript mainly because of security reason, so anyway you have to create some back end service which will update you with an list of available files in your folder. You don't have to do it in a hard way, you can use AJAX to it smoothly and nicely
You can't read a folder's contents, neither on the server nor on the clientside. What you can do is to read the folder's contents with the help of a serverside script, and load it to a JavaScript array while processing the page.
This would not be ideal but in the absence of server-side processing (which you really should be doing--either PHP or Rails or Perl or whatever your host supports), you could allow directory listing on your images folder. This has security implications. Then loading e.g., http://mysite.com/rotatingImages should respond with a list of files. You could do this with AJAX, parse out the relevant hrefs, push them onto an array and render your rotating images in JS.
You must send the list of names along with the JavaScript and then iterate through it.
A noted above, you can not access server's system from a client's browser (which is where JavaScript runs). You have 3 possible solutions: Create the JavaScript file via some dynamic back-end (php or perl scripts are best for that). The main JavaScript function could still be static but the initialization of the array used by it (either as a snippet on the main HTML page or a separate .js imported file) would be a php/perl generated URL. A recent StackOverflow discussion of the topic is at link text Make an XMLHttpRequest (AJAX) call from your JavaScript to a separate service (basically a URL backed by - again - php/perl backend script) returning XML/JSON/your_data_format_of_choice list of files. This is probably a better solution if you expect/want/need to refresh a frequently-changing list of images, whereas a pre-built list of files in solution #1 is better suited when you don't care about list of files changing while the web page is loaded into the browser. An un-orthodox solution - if browsers you care about support animated background images (gif/png), just compile your set of images, especially if they are small sized, into an animated gif/png and use that as background.