I'd like my HTML5 app to draw onto two different screens. This (I think) means that I need have two distinct browser windows, one on each screen. Is this possible? It seems that I'd really have to load the same app into two windows, and somehow have the windows communicate to each other. I couldn't find an example of how to accomplish this. How can I make it happen?
For extra wonderfulness: There may not be a server involved, just an app served locally from the filesystem.
There's no need for a messy polling infrastructure using local storage or (shudder) cookies. Assuming the two pages are served from the same domain, it is trivial to implement cross-window communication so long as the second window is opened by the first.
In your main window: (click here for JSFiddle demo and here for the secondary page's code)
var win2;
function openSecondaryWindow() {
return win2 = window.open('win2.html','secondary','width=300,height=100');
}
$(function() {
if (!openSecondaryWindow()) // Try to open the window. This will likely be blocked by a popup blocker (unless you disable it).
$(document.body) // If it is blocked, clicking a link usually allows the popup to be spawned.
.prepend('Popup blocked. Click here to open the secondary window.')
.click(function() { openSecondaryWindow(); return false; });
$('#somelink').click(function() {
if (win2) win2.doSomething(); // Obviously, you'll need to define doSomething in the second page
else alert('The secondary window is not open.');
return false;
});
});
Once the secondary window has been opened by your main window, win2 will refer to the window object of the second page – in other words, the page's global scope. You'll have access to all of the functions and variables you've defined in the second page.
Thus, you can pass data through function calls.
win2.update({ key: 'value', ... });
From your secondary window, you can call back to functions in the main window through the opener property, which will refer to the window object of your main window. (This is demonstrated in the JSFiddle demo.)
Update: Intercom is a library that uses local storage to implement broadcast messaging between windows. Local storage fires an event (onstorage) when data changes, so polling is not actually necessary. Intercom allows all pages on a domain to communicate, regardless of how they were opened.
This is probably something that you could use websockets for, have each window send its info back to the app and then have them updated, but those are not supported across all browsers and in fact I believe are currently removed from most builds due to security issues with the spec.
For offline apps if they were on the same domain, which I'm assuming they would be locally, you could use local storage or even cookies and then have the apps poll for changes to the storage api?
I have been doing some experiments with offline local storage myself recently and I'm able to maintain state between windows with Chrome locally, in firefox this does not work, but I believe it is fixed in the FF4 RC
Edit 2:
Slapped together a quick and dirty proof of concept in two files:
Index 1:
<body>
<div id="result">x</div>
</body>
<script type="text/javascript">
var i = 0;
function update(){
setTimeout(function(){
localStorage.setItem("bar", i);
document.getElementById('result').innerHTML = "localstorage set to " + localStorage.getItem("bar");
i++;
console.log(i);
update();
}, 2000);
}
update();
</script>
Index 2:
<body>
<div id="result">s</div>
</body>
<script type="text/javascript">
function update(){
setTimeout(function(){
document.getElementById('result').innerHTML = localStorage.getItem("bar");
update();
}, 1000);
}
update();
</script>
Opening them both up in different windows in Chrome locally, the second one will display the state that is set by the loop in the first window. Still not working in FireFox 4 I found (a Mozilla Dev swore to me yesterday that offline local storage works now, oh well). You can probably getting it working in IE via http://www.modernizr.com/ but I have yet to test that.
Related
Goal:
I have a ReactJs web app called "ImageGallery" that's currently integrated in our business suite system(which is a huge system contains older technologies, mainly web forms). In our system, we need to add a button to launch a browser window to spin the app.
ImageGallery app rely on the current system to pass data to it to show, which means, when the ImageGallery app loads, it calls a function on the business suite system side like
window.parent.loadData()
Since the data is passed in by JavaScript, there is no ajax call.
Code:
function showImage() {
var imageGalleryWindow = window.open('/ImageGallery', '_blank', 'height=' + window.screen.availHeight + ',width=' + window.screen.availWidth + ',scrollbars=yes,menubar=no,resizable=yes,toolbar=no,location=no,status=no');
imageGalleryWindow.loadData= loadData;
}
loadData() function is available on the current JavaScript file and will be passed to the new window so it can be used by the ReactJS app.
Problem:
The app works perfectly with Chrome/Firefox/Edge, but not IE 11. IE 11 sometimes loads, sometimes not. After taking a closer look, I found there's a race condition.
Basically whenever the app failed, loadData() function has not been passed to the newly created window. Again, that's only for IE11, all other browsers seems fine.
What I've tried:
I tried the following:
1> Tried to put a wait function in ImageGallery app like this:
static resolveDocuments(){
if(typeof window.parent.loadData === 'function'){
// do the work to show image
} else {
setTimeout(this.resolveDocuments, 2000);
}
}
I am using redux thunk, I didn't get any luck in getting this to work.
2> put imageGalleryWindow.loadData= loadData; into window.onload(), bad idea but tried anyways
3> put imageGalleryWindow.loadData= loadData; into imageViewerWindow.document.addEventListener("DOMContentLoaded", function (event) {} still bad idea but tried anyways
4> created a new ImageGallery.html, use an iframe to host ImageGallery app. In section, include the JavaScript file from parent. No luck
5> created a webform page ImageGallery.aspx, use an iframe to host ImageGallery app. In section, include the JavaScript file from parent. No luck.
Thanks for taking time to finish reading this problem, and please let me know if you have an idea I can try.
Thanks so much!
Ray
The solution I end up having is html 5 local storage:
Before calling window.open() to populate the new window, create a entry in html 5 local storage with all the data I need to pass in.
When the image gallery app loads, check if local storage items exists, if so, pull the data. The data will always be there, since it sets before new window generated, and since both pages are from the same domain, they could both access the same local storage.
To avoid security issues, after image gallery app read data from the local storage, I keep a copy of the data within the app, then delete the local storage entry.
Hope this can be helpful to other people.
I've got desktop application that works as simple browser. It has tabs with webviews that show web pages.
Some websites have social sign-in option. And I really need it to be available for my users.
In general browser (e.g. chrome) when you sign in via Facebook, it creates new window with social authorization form. After submitting this form the new window closes and parent window receives callback code, reloads and sign you in.
In my app when you try to do the same, webview that contains the web page, creates new BrowserWindow with the same auth form. At this point everything works fine. When you submit form, the new window closes but nothing happens after. Parent window doesn't receive any callback, the code that should run isn't triggered at all. Or probably it has been received, but not triggered, since if I reload page, it shows that I'm signed in.
According to the electron documentation <webview> runs in a separate process than renderer process. So I think when new window closes, the callback is received by parent BrowserWindow (renderer process) and not runs exactly in webview frame.
While searching through different sources for solution, I found that this problem is also concerned with window.opener, that was not fully supported in the earliest versions of electron. But according to the issue#1865 on github full window.opener support is now available if I open window by window.open method.
To make this work I should prevent webview to open new BrowserWindow and create it by myself.
The following code fails:
// in renderer process
webview.addEventListener('new-window', function(event) {
event.preventDefault();
// I also tried 'event.defaultPrevented = true;'
// or 'return false;'
});
The listener is triggered, but I can't prevent new-window opening.
Someone wrote that new-window can be prevented in main process only.
So I add the following code in my main.js:
// in main process
var electron = require('electron');
var BrowserWindow = electron.BrowserWindow;
mainWindow = new BrowserWindow({
"width": 970,
"height": 500,
"center": true,
'title': 'Main window',
"autoHideMenuBar": true
});
mainWindow.loadURL('file://' + root + '/index.html');
mainWindow.webContents.on('new-window', function(event) {
console.log(event);
});
This time event 'new-window' is not triggered at all. I think this is because the event works only if renderer process requests to open a new window by window.open for example. But in my case new window is opened by <webview> tag, not by renderer process. So I failed to prevent new-window to open since these are two separate processes.
So I decided just to get new-window after it opens.
// in renderer process
var electron = require("electron");
var remote = electron.remote;
webview.addEventListener('new-window', function(event) {
var win = remote.BrowserWindow.getFocusedWindow();
var contents = win.webContents;
win.setIcon(root+"/img/favicon.ico");
win.setMenuBarVisibility(false);
contents.on('dom-ready', function() {
contents.openDevTools();
})
});
After that I really have no idea what to do next... how to append window.opener and solve my issue
I found many similar issues on github. But all of them are merged to one issue #1865 and there is no clear answer so far.
I know solution exists. Brave developers fixed this. Brave browser also made with electron works correctly the same as general browser does. But I can't find answer while looking through its sources.
They even made tests to check whether window.opener works correctly. But I failed to install Brave from git repository. After 'npm-start' it says 'Error: %1 is not valid Win32 application'. I have Windows 10. (just another reason makes me switch to Linux. Hope to make it soon.)
So, my question is: how to send callback data from new window back to webview that created this window. Thanks in advance.
First, to handles new-window popups yourself, you have to disable allowpopups (i.e. remove allowpopups attribute from the webview)
This way the new-window event will be triggered but no popup will be opened until you explicitelly code it.
For the window.opener feature which is required for some social or sso login, electron does not have enough support of it. See issue 1865
The Brave team have implemented it in a restricted way in their fork of Electron. See issue 4175.
You may try to either:
Use the fork of electron from Brave (not sure it will fit your needs as they have made some major changes)
Make a PR to electron to integrate the support of window.opener from the fork.
Make your own polyfill for the methods you need by injecting a script that implements missing window.opener methods. You can use window.opener.send to communicate with renderer process)
I'm new at JavaScript, so can someone help me? How can I execute a command on another tab? For example, I have opened new tab from my main tab and opened translate.com (just for textbox) on it, and the problem is I don't know how to put text in search textbox?
I open the page with this command:
var page = window.open('https://www.translate.com/');
On the page, I can enter text with this code:
$string1 = $("#source_text");
$string1.val("text");
I have tried this, but this code doesn't work the way I want it to.
var page = window.open('https://www.translate.com/');
setTimeout(function(){page.f1()},20000);
setTimeout(function(){page.close()},30000);
function f1() {
$string1 = $("#source_text");
$string1.val("ka tu");
}
I find the function that you are trying to run very abnormal. So, let's start with small steps.
The page.close() function is perfect you can do that and it works. The other part won't work first of all because the page object created by window.open has no function called f1 on it.
Furthermore it is very important from where you are trying to run the script on the other window you always must take into consideration the cross-origin limitations. Easily explained if you try to run a function from a google.com tab on a separate window yahoo.com it won't work this is a security issue.
In order to run function f1 in that window it is important that f1 function is declared globally and ussualy you try and do this the following way page.window.f1() - and there you have it. So for example your code would be
page.window.$("#source_text").val('something');
refactoring your code would be like this:
var page = window.open('https://www.translate.com/');
setTimeout(function(){ page.window.$("#source_text").val('something');},20000);
setTimeout(function(){page.close()},30000);
open translate.com in a tab open dev tools in chrome and paste the above code in the console tab and see the results, it will work.
I recommend that before running code in another window you should check that that window is loaded first of all (in your case works) because of the long timeout.
A better solution would be using HTML5's postMessage API: check this link here for a good tutorial: https://robertnyman.com/2010/03/18/postmessage-in-html5-to-send-messages-between-windows-and-iframes/ - also this meens of course that the window you are opening is listening for some sort of postMessages.
In general you do this things with hosts that you manage and not other hosts because it might not work always. Also it will always work if you are on a host and open the same host in another window otherwise you end up with this security error: VM88:2 Uncaught SecurityError: Blocked a frame with origin "https://some_other_host" from accessing a frame with origin "https://www.translate.com". Protocols, domains, and ports must match.
Hope this helps, and you understand the way it works now.
Cheers
I am trying to allow the user to configure my safari extension through a HTML preference page (as many Safari extensions do). I open this page from a javascript call inside my global html file:
var newTab = safari.application.activeBrowserWindow.openTab();
newTab.url = safari.extension.baseURI + "settings/settings.html";
What I can NOT manage to do is write anything from this settings.html into the actual Safari extension settings or access the global page.
safari.extension.settings.MY_SETTINGS = settingsData;
safari.extension.globalPage
Both of these calls result in exceptions and the objects appear undefined.
I then also tried to send messages, but never seem to receive them in the global space, where I thought I could then again access the settings.
safari.self.tab.dispatchMessage("store_settings", settingsData); //settings javascript
These message are not received by my event listener.
safari.self.addEventListener("message", process_messages, false); //GLOBAL javascript
Any idea why I can not access the extension settings? Do I need to initialise something for my settings.html to be able to access the extension settings?
PS: I have seen a similar approach working inside the ClickToPlugin Safari extension - so it should be possible, but I can't get it to work :(
In the global script, try safari.application.addEventListener.
If your html page is part of your extension then your settings.js script file will have access to safari.extension.globalPage. This object points to the window of your global.html.
From there you can call any object in that context. Debugging this however is a pain to say the least. Good luck :-)
I'm developing an extension in Chrome 4 (currently 4.0.249.0) that will show the user's StackOverflow/SuperUser/ServerFault reputation in the status bar. I've designed an options page to get the user's profile IDs and I save them to localStorage and read them well in the extension. It all works great.
The problem is I cannot find a (programmatic) way to refresh the extension upon options saving. I tried calling location.reload(); from the extension page itself upon right clicking it - to no avail. I pursued it further and tried looking at what Chrome's chrome://extensions/ page does to reload an extension, and found this code:
/**
* Handles a 'reload' button getting clicked.
*/
function handleReloadExtension(node) {
// Tell the C++ ExtensionDOMHandler to reload the extension.
chrome.send('reload', [node.extensionId]);
}
Copying this code to my event handler did not help (and yes, I tried replacing [node.extensionId] with the actual code). Can someone please assist me in doing this the right way, or pointing me at a code of an extension that does this correctly? Once done, I'll put the extension and its source up on my blog.
Now the simplest way to make extension to reload itself is to call chrome.runtime.reload(). This feature doesn't need any permissions in manifest.
To reload another extension use chrome.management.setEnabled(). It requires "permissions": [ "management" ] in manifest.
window.location.reload() works for me
I am using chromium 6.x so it might be fixed in newer version
The chrome.send function is not accessible by your extension's javascript code, pages like the newtab page, history and the extensions page use it to communicate with the C++ controller code for those pages.
You can push updates of your extension to users who have it installed, this is described here. The user's application will be updated once the autoupdate interval is hit or when they restart the browser. You cannot however reload a user's extension programmatically. I think that would be a security risk.
I just had this same problem with an extension.
Turns out you can listen for storage changes within background.js using chrome.storage.onChanged and have that perform the refresh logic.
For example:
// Perform a reload any time the user clicks "Save"
chrome.storage.onChanged.addListener(function(changes, namespace) {
chrome.storage.sync.get({
profileId: 0
}, function(items) {
// Update status bar text here
});
});
You could also reload parts of your extension this way by taking the changes parameter into account. chrome.runtime.reload() might be easier, but this has less overhead.
For all future Googlers - the Browser Extension spec now includes runtime.reload() - https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/reload
For Chrome, you might need to use chrome.runtime.reload() (but I'd handle such cases via Mozilla's awesome webextension-polyfill).