embed plugin windows within OSX browsers properly

This commit is contained in:
David Rose 2009-07-13 07:14:30 +00:00
parent 37e76f402d
commit 9fa212a48a
17 changed files with 414 additions and 60 deletions

View File

@ -6,6 +6,8 @@
#define USE_PACKAGES tinyxml openssl zlib jpeg x11
#define TARGET p3d_plugin
#define LIB_PREFIX
#define OTHER_LIBS subprocbuffer
// We need this because we don't
// include dtool_config.h.

View File

@ -39,7 +39,7 @@ inline HandleStream::
// output.
////////////////////////////////////////////////////////////////////
inline void HandleStream::
open_read(Handle handle) {
open_read(FHandle handle) {
clear((ios::iostate)0);
_buf.open_read(handle);
if (!_buf.is_open_read()) {
@ -55,7 +55,7 @@ open_read(Handle handle) {
// output.
////////////////////////////////////////////////////////////////////
inline void HandleStream::
open_write(Handle handle) {
open_write(FHandle handle) {
clear((ios::iostate)0);
_buf.open_write(handle);
if (!_buf.is_open_write()) {

View File

@ -29,8 +29,8 @@ public:
inline HandleStream();
inline ~HandleStream();
inline void open_read(Handle handle);
inline void open_write(Handle handle);
inline void open_read(FHandle handle);
inline void open_write(FHandle handle);
inline void close();
private:

View File

@ -69,7 +69,7 @@ HandleStreamBuf::
// output.
////////////////////////////////////////////////////////////////////
void HandleStreamBuf::
open_read(Handle handle) {
open_read(FHandle handle) {
close();
_handle = handle;
@ -84,7 +84,7 @@ open_read(Handle handle) {
// output.
////////////////////////////////////////////////////////////////////
void HandleStreamBuf::
open_write(Handle handle) {
open_write(FHandle handle) {
close();
_handle = handle;

View File

@ -17,10 +17,10 @@
#ifdef _WIN32
#include <windows.h>
typedef HANDLE Handle;
typedef HANDLE FHandle;
#else
// On POSIX, we use a file descriptor as a "handle".
typedef int Handle;
typedef int FHandle;
#endif
#include <iostream>
@ -37,8 +37,8 @@ public:
HandleStreamBuf();
virtual ~HandleStreamBuf();
void open_read(Handle handle);
void open_write(Handle handle);
void open_read(FHandle handle);
void open_write(FHandle handle);
bool is_open_read() const;
bool is_open_write() const;
void close();
@ -56,7 +56,7 @@ private:
bool _is_open_read;
bool _is_open_write;
Handle _handle;
FHandle _handle;
char *_buffer;
};

View File

@ -69,6 +69,7 @@ P3D_instance_get_request_func *P3D_instance_get_request;
P3D_check_request_func *P3D_check_request;
P3D_request_finish_func *P3D_request_finish;
P3D_instance_feed_url_stream_func *P3D_instance_feed_url_stream;
P3D_instance_handle_event_func *P3D_instance_handle_event;
#ifdef _WIN32
static HMODULE module = NULL;
@ -206,6 +207,7 @@ load_plugin(const string &p3d_plugin_filename) {
P3D_check_request = (P3D_check_request_func *)get_func(module, "P3D_check_request");
P3D_request_finish = (P3D_request_finish_func *)get_func(module, "P3D_request_finish");
P3D_instance_feed_url_stream = (P3D_instance_feed_url_stream_func *)get_func(module, "P3D_instance_feed_url_stream");
P3D_instance_handle_event = (P3D_instance_handle_event_func *)get_func(module, "P3D_instance_handle_event");
#undef get_func
@ -244,7 +246,8 @@ load_plugin(const string &p3d_plugin_filename) {
P3D_instance_get_request == NULL ||
P3D_check_request == NULL ||
P3D_request_finish == NULL ||
P3D_instance_feed_url_stream == NULL) {
P3D_instance_feed_url_stream == NULL ||
P3D_instance_handle_event == NULL) {
cerr
<< "Some function pointers not found:"
@ -283,6 +286,7 @@ load_plugin(const string &p3d_plugin_filename) {
<< "\nP3D_check_request = " << P3D_check_request
<< "\nP3D_request_finish = " << P3D_request_finish
<< "\nP3D_instance_feed_url_stream = " << P3D_instance_feed_url_stream
<< "\nP3D_instance_handle_event = " << P3D_instance_handle_event
<< "\n";
return false;
}
@ -376,6 +380,7 @@ unload_dso() {
P3D_check_request = NULL;
P3D_request_finish = NULL;
P3D_instance_feed_url_stream = NULL;
P3D_instance_handle_event = NULL;
plugin_loaded = false;
}

View File

@ -55,6 +55,7 @@ extern P3D_instance_get_request_func *P3D_instance_get_request;
extern P3D_check_request_func *P3D_check_request;
extern P3D_request_finish_func *P3D_request_finish;
extern P3D_instance_feed_url_stream_func *P3D_instance_feed_url_stream;
extern P3D_instance_handle_event_func *P3D_instance_handle_event;
string get_plugin_basename();
bool load_plugin(const string &p3d_plugin_filename);

View File

@ -27,15 +27,17 @@
#include <sstream>
#include <algorithm>
#ifdef __APPLE__
#include <sys/mman.h>
#endif // __APPLE__
#ifdef _WIN32
typedef P3DWinSplashWindow SplashWindowType;
#else
#ifdef HAVE_X11
#elif defined(HAVE_X11)
typedef P3DX11SplashWindow SplashWindowType;
#else
typedef P3DSplashWindow SplashWindowType;
#endif
#endif
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::Constructor
@ -63,6 +65,13 @@ P3DInstance(P3D_request_ready_func *func, void *user_data) :
_instance_window_opened = false;
_requested_stop = false;
#ifdef __APPLE__
_shared_fd = -1;
_shared_mmap_size = 0;
_swbuffer = NULL;
_reversed_buffer = NULL;
#endif // __APPLE__
// Set some initial properties.
_panda_script_object->set_float_property("downloadProgress", 0.0);
}
@ -95,6 +104,19 @@ P3DInstance::
_splash_window = NULL;
}
#ifdef __APPLE__
if (_swbuffer != NULL) {
SubprocessWindowBuffer::destroy_buffer(_shared_fd, _shared_mmap_size,
_shared_filename, _swbuffer);
_swbuffer = NULL;
}
if (_reversed_buffer != NULL) {
delete[] _reversed_buffer;
_reversed_buffer = NULL;
}
#endif
DESTROY_LOCK(_request_lock);
// TODO: empty _raw_requests and _baked_requests queues, and
@ -184,6 +206,24 @@ set_wparams(const P3DWindowParams &wparams) {
xcommand->SetAttribute("cmd", "setup_window");
xcommand->SetAttribute("instance_id", get_instance_id());
TiXmlElement *xwparams = _wparams.make_xml();
#ifdef __APPLE__
// On Mac, we have to communicate the results of the rendering
// back via shared memory, instead of directly parenting windows
// to the browser. Set up this mechanism.
int x_size = _wparams.get_win_width();
int y_size = _wparams.get_win_height();
nout << "size = " << x_size << " * " << y_size << "\n" << flush;
if (_shared_fd == -1 && x_size != 0 && y_size != 0) {
_swbuffer = SubprocessWindowBuffer::new_buffer
(_shared_fd, _shared_mmap_size, _shared_filename, x_size, y_size);
if (_swbuffer != NULL) {
_reversed_buffer = new char[_swbuffer->get_framebuffer_size()];
}
xwparams->SetAttribute("subprocess_window", _shared_filename);
}
#endif // __APPLE__
doc->LinkEndChild(decl);
doc->LinkEndChild(xcommand);
@ -425,6 +465,85 @@ feed_url_stream(int unique_id,
return download_ok;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::handle_event
// Access: Public
// Description: Responds to the os-generated window event.
////////////////////////////////////////////////////////////////////
void P3DInstance::
handle_event(P3D_event_data event) {
#ifdef _WIN32
// This function is not used in Win32 and does nothing.
#elif defined(__APPLE__)
EventRecord *er = event._event;
switch (er->what) {
case nullEvent:
break;
case mouseDown:
case mouseUp:
{
Point pt = er->where;
GlobalToLocal(&pt);
P3D_window_handle window = _wparams.get_parent_window();
cerr << "mouse " << pt.h << " " << pt.v << "\n";
if (_swbuffer != NULL) {
SubprocessWindowBuffer::Event swb_event;
swb_event._up = (er->what == mouseUp);
swb_event._x = pt.h;
swb_event._y = pt.v;
_swbuffer->add_event(swb_event);
}
}
break;
case keyDown:
case keyUp:
case autoKey:
cerr << "keycode: " << (er->message & 0xffff) << "\n";
break;
case updateEvt:
paint_window();
break;
case activateEvt:
cerr << "activate window: " << er->message << "\n";
break;
case diskEvt:
break;
case osEvt:
if ((er->message & 0xf0000000) == suspendResumeMessage) {
if (er->message & 1) {
cerr << "suspend\n";
} else {
cerr << "resume\n";
}
} else if ((er->message & 0xf0000000) == mouseMovedMessage) {
cerr << "mouse moved\n";
} else {
cerr << "unhandled osEvt: " << hex << er->message << dec << "\n";
}
break;
case kHighLevelEvent:
cerr << "high level: " << er->message << "\n";
break;
default:
cerr << "unhandled event: " << er->what << ", " << er->message << "\n";
break;
}
#elif defined(HAVE_X11)
#endif
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::add_package
// Access: Public
@ -811,6 +930,81 @@ install_progress(P3DPackage *package, double progress) {
_panda_script_object->set_float_property("downloadProgress", progress);
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::paint_window
// Access: Private
// Description: Actually paints the rendered image to the browser
// window. This is only implemented (and needed) for
// OSX, where the child process isn't allowed to do it
// directly.
////////////////////////////////////////////////////////////////////
void P3DInstance::
paint_window() {
#ifdef __APPLE__
if (_swbuffer == NULL) {
nout << "no _swbuffer\n";
return;
}
QDErr err;
// blit rendered framebuffer into window backing store
int x_size = min(_wparams.get_win_width(), _swbuffer->get_x_size());
int y_size = min(_wparams.get_win_height(), _swbuffer->get_y_size());
Rect src_rect = {0, 0, y_size, x_size};
Rect ddrc_rect = {0, 0, y_size, x_size};
size_t rowsize = _swbuffer->get_row_size();
if (_swbuffer->ready_for_read()) {
// Copy the new framebuffer image from the child process.
const void *framebuffer = _swbuffer->open_read_framebuffer();
// We have to reverse the image vertically first (different
// conventions between Panda and Mac).
for (int yi = 0; yi < y_size; ++yi) {
memcpy(_reversed_buffer + (y_size - 1 - yi) * rowsize,
(char *)framebuffer + yi * rowsize,
rowsize);
}
_swbuffer->close_read_framebuffer();
} else {
// No frame ready. Just re-paint the frame we had saved last
// time.
}
// create a GWorld containing our image
GWorldPtr pGWorld;
err = NewGWorldFromPtr(&pGWorld, k32BGRAPixelFormat, &src_rect, 0, 0, 0,
_reversed_buffer, rowsize);
if (err != noErr) {
nout << " error in NewGWorldFromPtr, called from paint_window()\n";
return;
}
GrafPtr out_port = _wparams.get_parent_window()._port;
GrafPtr portSave = NULL;
Boolean portChanged = QDSwapPort(out_port, &portSave);
// Make sure the clipping rectangle isn't in the way. Is there a
// better way to eliminate the cliprect from consideration?
Rect r = { 0, 0, 0x7fff, 0x7fff };
ClipRect(&r);
CopyBits(GetPortBitMapForCopyBits(pGWorld),
GetPortBitMapForCopyBits(out_port),
&src_rect, &ddrc_rect, srcCopy, 0);
if (portChanged) {
QDSwapPort(portSave, NULL);
}
DisposeGWorld(pGWorld);
#endif // __APPLE__
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstance::SplashDownload::Constructor
// Access: Public

View File

@ -22,6 +22,10 @@
#include "p3dReferenceCount.h"
#include "get_tinyxml.h"
#ifdef __APPLE__
#include "subprocessWindowBuffer.h"
#endif
#include <deque>
#include <map>
@ -65,6 +69,8 @@ public:
const unsigned char *this_data,
size_t this_data_size);
void handle_event(P3D_event_data event);
inline int get_instance_id() const;
inline const string &get_session_key() const;
inline const string &get_python_version() const;
@ -101,6 +107,8 @@ private:
void make_splash_window();
void install_progress(P3DPackage *package, double progress);
void paint_window();
P3D_request_ready_func *_func;
P3D_object *_browser_script_object;
P3DToplevelObject *_panda_script_object;
@ -118,6 +126,17 @@ private:
// Not ref-counted: session is the parent.
P3DSession *_session;
#ifdef __APPLE__
// On OSX, we have to get a copy of the framebuffer data back from
// the child process, and draw it to the window, here in the parent
// process. Crazy!
int _shared_fd;
size_t _shared_mmap_size;
string _shared_filename;
SubprocessWindowBuffer *_swbuffer;
char *_reversed_buffer;
#endif __APPLE__
P3DSplashWindow *_splash_window;
bool _instance_window_opened;

View File

@ -889,27 +889,37 @@ setup_window(P3DCInstance *inst, TiXmlElement *xwparams) {
xwparams->Attribute("win_height", &win_height);
long parent_window_handle = 0;
const char *subprocess_window = "";
#ifdef _WIN32
int hwnd;
if (xwparams->Attribute("parent_hwnd", &hwnd)) {
parent_window_handle = (long)hwnd;
}
#endif
#ifdef HAVE_X11
#elif __APPLE__
// On Mac, we don't parent windows directly to the browser; instead,
// we have to go through this subprocess-window nonsense.
subprocess_window = xwparams->Attribute("subprocess_window");
if (subprocess_window == NULL) {
subprocess_window = "";
}
#elif defined(HAVE_X11)
// Bad! Casting to int loses precision.
int xwindow;
if (xwparams->Attribute("parent_xwindow", &xwindow)) {
parent_window_handle = (unsigned long)xwindow;
parent_window_handle = (long)xwindow;
}
#endif
// TODO: direct this into the particular instance. This will
// require a specialized ShowBase replacement.
PyObject *result = PyObject_CallMethod
(_runner, (char *)"setupWindow", (char *)"siiiii", window_type.c_str(),
(_runner, (char *)"setupWindow", (char *)"siiiiis", window_type.c_str(),
win_x, win_y, win_width, win_height,
parent_window_handle);
parent_window_handle, subprocess_window);
if (result == NULL) {
PyErr_Print();
}

View File

@ -79,8 +79,11 @@ make_xml() {
xwparams->SetAttribute("win_height", _win_height);
#ifdef _WIN32
xwparams->SetAttribute("parent_hwnd", (int)_parent_window._hwnd);
#endif
#ifdef HAVE_X11
#elif defined(__APPLE__)
// The subprocess_window setting is applied by the caller.
#elif defined(HAVE_X11)
xwparams->SetAttribute("parent_xwindow", (unsigned long)_parent_window._xwindow);
#endif
break;

View File

@ -406,3 +406,11 @@ P3D_instance_feed_url_stream(P3D_instance *instance, int unique_id,
RELEASE_LOCK(_api_lock);
return result;
}
void
P3D_instance_handle_event(P3D_instance *instance, P3D_event_data event) {
assert(P3DInstanceManager::get_global_ptr()->is_initialized());
ACQUIRE_LOCK(_api_lock);
((P3DInstance *)instance)->handle_event(event);
RELEASE_LOCK(_api_lock);
}

View File

@ -57,6 +57,10 @@
#endif /* _WIN32 */
#ifdef __APPLE__
#include <Carbon/Carbon.h>
#endif /* __APPLE__ */
#ifdef __cplusplus
extern "C" {
#endif
@ -130,14 +134,14 @@ typedef struct {
typedef struct {
#ifdef _WIN32
HWND _hwnd;
#endif
#ifdef HAVE_X11
#elif defined(__APPLE__)
GrafPtr _port;
#elif defined(HAVE_X11)
unsigned long _xwindow;
void *_xdisplay;
#endif
#ifdef __APPLE__
void *_nswindow;
#endif
} P3D_window_handle;
/* This enum lists the different kinds of window types that may be
@ -781,6 +785,29 @@ P3D_instance_feed_url_stream_func(P3D_instance *instance, int unique_id,
const void *this_data,
size_t this_data_size);
/* This structure abstracts out the event pointer data types for the
different platforms, as passed to P3D_instance_handle_event(),
below. */
typedef struct {
#ifdef _WIN32
// Not used for Win32.
#elif defined(__APPLE__)
EventRecord *_event;
#elif defined(HAVE_X11)
// XEvent *_event; ?
#endif
} P3D_event_data;
/* Use this function to supply a new os-specific window event to the
plugin. This is presently used only on OSX, where the window
events are needed for proper plugin handling; and possibly also on
X11. On Windows, window events are handled natively by the plugin.
*/
typedef void
P3D_instance_handle_event_func(P3D_instance *instance, P3D_event_data event);
#ifdef P3D_FUNCTION_PROTOTYPES
/* Define all of the actual prototypes for the above functions. */
@ -820,6 +847,7 @@ EXPCL_P3D_PLUGIN P3D_instance_get_request_func P3D_instance_get_request;
EXPCL_P3D_PLUGIN P3D_check_request_func P3D_check_request;
EXPCL_P3D_PLUGIN P3D_request_finish_func P3D_request_finish;
EXPCL_P3D_PLUGIN P3D_instance_feed_url_stream_func P3D_instance_feed_url_stream;
EXPCL_P3D_PLUGIN P3D_instance_handle_event_func P3D_instance_handle_event;
#endif /* P3D_FUNCTION_PROTOTYPES */

View File

@ -438,7 +438,7 @@ handle_request(P3D_request *request) {
////////////////////////////////////////////////////////////////////
// Function: PPInstance::handle_request_loop
// Access: Private, Static
// Access: Public, Static
// Description: Checks for any new requests from the plugin, and
// dispatches them to the appropriate PPInstance. This
// function is called only in the main thread.
@ -461,6 +461,47 @@ handle_request_loop() {
}
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::handle_event
// Access: Public
// Description: Called by the browser as new window events are
// generated.
////////////////////////////////////////////////////////////////////
void PPInstance::
handle_event(void *event) {
// This is a good time to check for new requests.
handle_request_loop();
if (_p3d_inst == NULL) {
// Ignore events that come in before we've launched the instance.
return;
}
#ifdef __APPLE__
EventRecord *er = (EventRecord *)event;
switch (er->what) {
case nullEvent:
// We appear to get this event pretty frequently when nothing else
// is going on. Great; we'll take advantage of it to invalidate
// the instance rectangle, which will cause updateEvt to be
// triggered (if the instance is still onscreen).
if (_got_window) {
NPRect rect = { 0, 0, _window.height, _window.width };
browser->invalidaterect(_npp_instance, &rect);
}
break;
}
// All other events we feed down to the plugin for processing.
P3D_event_data edata;
edata._event = er;
P3D_instance_handle_event(_p3d_inst, edata);
#endif // __APPLE__
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::get_panda_script_object
// Access: Public
@ -972,14 +1013,24 @@ send_window() {
// right spot.
#ifdef _WIN32
parent_window._hwnd = (HWND)(_window.window);
#endif
#ifdef HAVE_X11
x = 0;
y = 0;
#elif defined(__APPLE__)
logfile << "windowed plugin\n" << flush;
NP_Port *port = (NP_Port *)_window.window;
logfile << "portx, porty = " << port->portx << ", " << port->porty << "\n";
logfile << "x, y = " << _window.x << ", " << _window.y << "\n";
parent_window._port = port->port;
#elif defined(HAVE_X11)
// We make it an 'unsigned long' instead of 'Window'
// to avoid nppanda3d.so getting a dependency on X11.
parent_window._xwindow = (unsigned long)(_window.window);
#endif
x = 0;
y = 0;
#endif
} else {
// We have a "windowless" plugin. Parent our window directly to
// the browser window.
@ -990,8 +1041,15 @@ send_window() {
&hwnd) == NPERR_NO_ERROR) {
parent_window._hwnd = hwnd;
}
#endif
#ifdef HAVE_X11
#elif defined(__APPLE__)
logfile << "windowless plugin\n" << flush;
NP_Port *port = (NP_Port *)_window.window;
logfile << "portx, porty = " << port->portx << ", " << port->porty << "\n";
logfile << "x, y = " << _window.x << ", " << _window.y << "\n";
parent_window._port = port->port;
#elif defined(HAVE_X11)
parent_window._xwindow = 0;
unsigned long win;
if (browser->getvalue(_npp_instance, NPNVnetscapeWindow,

View File

@ -53,6 +53,8 @@ public:
void handle_request(P3D_request *request);
static void handle_request_loop();
void handle_event(void *event);
NPObject *get_panda_script_object();
void p3dobj_to_variant(NPVariant *result, P3D_object *object);

View File

@ -331,8 +331,10 @@ int16
NPP_HandleEvent(NPP instance, void *event) {
// logfile << "HandleEvent\n" << flush;
// Here's a fine opportunity to check for new requests.
PPInstance::handle_request_loop();
PPInstance *inst = (PPInstance *)(instance->pdata);
assert(inst != NULL);
inst->handle_event(event);
return 0;
}

View File

@ -258,8 +258,36 @@ class AppRunner(DirectObject):
self.startIfReady()
def setupWindow(self, windowType, x, y, width, height, parent):
print "setupWindow %s, %s, %s, %s, %s, %s" % (windowType, x, y, width, height, parent)
def clearWindowPrc(self):
""" Clears the windowPrc file that was created in a previous
call to setupWindow(), if any. """
if self.windowPrc:
unloadPrcFile(self.windowPrc)
self.windowPrc = None
def setupWindow(self, windowType, x, y, width, height,
parent, subprocessWindow):
""" Applies the indicated window parameters to the prc
settings, for future windows; or applies them directly to the
main window if the window has already been opened. """
print "setupWindow %s, %s, %s, %s, %s, %s, %s" % (windowType, x, y, width, height, parent, subprocessWindow)
if self.started and base.win:
# If we've already got a window, this must be a
# resize/reposition request.
wp = WindowProperties()
if x or y or windowType == 'embedded':
wp.setOrigin(x, y)
if width or height:
wp.setSize(width, height)
base.win.requestProperties(wp)
return
# If we haven't got a window already, start 'er up. Apply the
# requested setting to the prc file.
if windowType == 'hidden':
data = 'window-type none\n'
else:
@ -271,33 +299,21 @@ class AppRunner(DirectObject):
data += 'fullscreen 0\n'
if windowType == 'embedded':
data += 'parent-window-handle %s\n' % (parent)
data += 'parent-window-handle %s\nsubprocess-window %s\n' % (
parent, subprocessWindow)
else:
data += 'parent-window-handle 0\n'
data += 'parent-window-handle 0\nsubprocess-window \n'
if x or y or windowType == 'embedded':
data += 'win-origin %s %s\n' % (x, y)
if width or height:
data += 'win-size %s %s\n' % (width, height)
if self.windowPrc:
unloadPrcFile(self.windowPrc)
self.clearWindowPrc()
self.windowPrc = loadPrcFileData("setupWindow", data)
if self.started and base.win:
# If we've already got a window, this must be a
# resize/reposition request.
wp = WindowProperties()
if x or y or windowType == 'embedded':
wp.setOrigin(x, y)
if width or height:
wp.setSize(width, height)
base.win.requestProperties(wp)
else:
# If we haven't got a window already, start 'er up.
self.gotWindow = True
self.startIfReady()
self.gotWindow = True
self.startIfReady()
def setRequestFunc(self, func):
""" This method is called by the plugin at startup to supply a
@ -319,9 +335,15 @@ class AppRunner(DirectObject):
successfully opened. """
if not self.windowOpened:
self.notifyRequest('onwindowopen')
self.windowOpened = True
# Now that the window is open, we don't need to keep those
# prc settings around any more.
self.clearWindowPrc()
# Inform the plugin and browser.
self.notifyRequest('onwindowopen')
def notifyRequest(self, message):
""" Delivers a notify request to the browser. This is a "this
happened" type notification; it optionally triggers some
@ -340,10 +362,10 @@ class AppRunner(DirectObject):
def evalScript(self, expression, needsResponse = False):
""" Evaluates an arbitrary JavaScript expression in the global
DOM space. This may be deferred if necessary if self.dom has
not yet been assigned. If needsResponse is true, this waits
for the value and returns it, which means it may not be
deferred. """
DOM space. This may be deferred if necessary if needsResponse
is False and self.dom has not yet been assigned. If
needsResponse is true, this waits for the value and returns
it, which means it cannot be deferred. """
if not self.dom:
# Defer the expression.