I'm trying to update my
htmlpreso
HTML presentation slide system to allow for a separate notes window.
Up to now, I've just used display mirroring. I connect to the
projector at 1024x768, and whatever is on the first (topmost/leftmost)
1024x768 pixels of my laptop screen shows on the projector. Since my
laptop screen is wider than 1024 pixels, I can put notes to myself
to the right of the slide, and I'll see them but the audience won't.
That works fine, but I'd like to be able to make the screens completely
separate, so I can fiddle around with other things while still
displaying a slide on the projector. But since my slides are in HTML,
and I still want my presenter notes, that requires putting the notes
in a separate window, instead of just to the right of each slide.
The notes for each slide are in a
<div id="notes">
on each page. So all I have to do is pop up another browser window
and mirror whatever is in that div to the new window, right?
Sure ...
except this is JavaScript, so nothing is simple. Every little thing
is going to be multiple days of hair-tearing frustration, and this
was no exception.
I should warn you up front that I eventually found a much simpler way
of doing this. I'm documenting this method anyway because it seems
useful to be able to communicate between two windows, but if you
just want a simple solution for the "pop up notes in another window"
problem, stay tuned for Part 2.
Step 0: Give Up On
file:
Normally I use
file:
URLs for presentations. There's no need
to run a web server, and in fact, on my lightweight netbook I usually
don't start apache2 by default, only if I'm actually working on
web development.
But most of the methods of communicating between windows don't work in
file
URLs, because of the "same-origin policy".
That policy is a good security measure: it ensures that a page
from innocent-url.com can't start popping up windows with content
from evilp0wnU.com without you knowing about it. I'm good with that.
The problem is that
file:
URLs have
location.origin
of
null
, and every null-origin window is considered to be a
different origin -- even if they're both from the same directory. That
makes no sense to me, but there seems to be no way around it. So if I
want notes in a separate window, I have to run a web server and use
http://localhost
.
Step 1: A Separate Window
The first step is to pop up the separate notes window, or get a
handle to it if it's already up.
JavaScript offers
window.open()
, but there's a trick:
if you just call
notewin = window.open("notewin.html", "notewindow")
you'll actually get a new tab, not a new window. If you actually
want a window, the secret code for that is to give it a size:
notewin = window.open("notewin.html", "notewindow",
"width=800,height=500");
There's apparently no way to just get a handle to an existing window.
The only way is to call
window.open()
,
pop up a new window if it wasn't there before, or reloads it if it's
already there.
I saw some articles implying that passing an empty string
""
as the first argument would return a handle to an existing window without
changing it, but it's not true: in Firefox and Chromium, at least,
that makes the existing window load
about:blank
instead of
whatever page it already has. So just give it the same page every time.
Step 2: Figure Out When the Window Has Loaded
There are several ways to change the content in the popup window from
the parent, but they all have one problem:
if you update the content right away after calling
window.open
,
whatever content you put there will be overwritten immediately when
the popup reloads its
notewin.html
page (or even
about:blank
).
So you need to wait until the popup is finished loading.
That sounds suspiciously easy. Assuming you have a function called
updateNoteWinContent()
, just do this:
// XXX This Doesn't work:
notewin.addEventListener('load', updateNoteWinContent, false);
Except it turns out the "load" event listener isn't called on reloads,
at least not in popups.
So this will work the first time, when the
note window first pops up, but never after that.
I tried other listeners, like "DOMContentLoaded" and
"readystatechange", but none of them are called on reload.
Why not? Who knows?
It's possible this is because the listener gets set too early, and
then is wiped out when the page reloads, but that's just idle
speculation.
For a while, I thought I was going to have to resort to an ugly hack:
sleep for several seconds in the parent window to give the popup time
to load:
await new Promise(r => setTimeout(r, 3000));
(requires declaring the calling function as
async
).
This works, but ... ick.
Fortunately, there's a better way.
Step 2.5: Simulate onLoad with postMessage
What finally worked was a tricky way to use
postMessage()
in reverse. I'd already experimented with using
postMessage()
from the parent window to the popup, but it didn't work because the
popup was still loading and wasn't ready for the content.
What works is to go the other way. In the code loaded by the popup
(
notewin.html
in this example), put some code at the end
of the page that calls
window.opener.postMessage("Loaded");
Then in the parent, handle that message, and don't try to update the
popup's content until you've gotten the message:
function receiveMessageFromPopup(event) {
console.log("Parent received a message from the notewin:", event.data);
// Optionally, check whether event.data == "Loaded"
// if you want to support more than one possible message.
// Update the "notes" div in the popup notewin:
var noteDiv = notewin.document.getElementById("notes");
noteDiv.innerHTML = "
Here is some content.
";
window.addEventListener("message", receiveMessageFromPopup, false);
Here's a complete working test:
Test of
Persistent Popup Window
.
In the end, though, this didn't solve my presentation problem.
I got it all debugged and working, only to discover that
postMessage
doesn't work in QtWebEngine, so
I couldn't use it in my slide presentation app.
Fortunately, I found a couple of other ways: stay tuned for Part 2.
(Update:
Part 2: A Clever Hack
.)
Debugging Multiple Windows: Separate Consoles
A note on debugging:
One thing that slowed me down was that JS I put in the popup didn't
seem to be running: I never saw its
console.log()
messages.
It took me a while to realize that each window has its own web console,
both in Firefox and Chromium. So you have to wait until the popup has
opened before you can see any debugging messages for it. Even then,
the popup window doesn't have a menu, and its context menu doesn't
offer a console window option. But it does offer
Inspect element
,
which brings up a Developer Tools window where you can click on
the
Console
tab to see errors and debugging messages.
Tags:
programming
,
web
,
javascript
[
20:29 Jan 05, 2020
More tech/web
|
permalink to this entry
|