If you think a bug might affect users in the 57 release, please set the correct tracking and status flags for Release Management.

document.written HTML element not available to external script

VERIFIED FIXED in mozilla0.8

Status

()

Core
DOM: Core & HTML
P3
normal
VERIFIED FIXED
17 years ago
9 years ago

People

(Reporter: Martin Honnen, Assigned: harishd)

Tracking

Trunk
mozilla0.8
Points:
---

Firefox Tracking Flags

(Not tracked)

Details

Attachments

(4 attachments)

(Reporter)

Description

17 years ago
When I document.write an HTML element in script loaded via <SCRIPT SRC the
created element is not accessible with
  document.getElementById
Here is an example:

<HTML>
<HEAD>
</HEAD>
<BODY>
<SCRIPT SRC="test14122000.js">
</SCRIPT>
<SCRIPT>
alert("in inline script: document.getElementById('aDiv')=" +
document.getElementById('aDiv'));
</SCRIPT>
</BODY>
</HTML>

with the js file

document.write('<DIV ID="aDiv">Kibology for all.<\/DIV>');
alert("in external script: document.getElementById('aDiv')=" +
document.getElementById('aDiv'));

The first alert shows null, the second correctly the div element.
(Reporter)

Comment 1

17 years ago
Created attachment 21062 [details]
needed for bug demo
(Reporter)

Comment 2

17 years ago
Created attachment 21063 [details]
bug demo (div is document.written but not accessible to script)
This happens because the parser caches document.write() calls and doesn't parse
the input data until it really needs to or untill there's more of it than it
wans to cache. I'm not sure there's a whole lot we wanna do about this since
disabling the caching would probably kill document.write() performance in mozilla.

Harish, do you know how/what/where/when this could be fixed? Is there something
we could call on the parser to force a "flush" of the document.write() written
data? Or do you see an other solution to this problem?
*** Bug 63510 has been marked as a duplicate of this bug. ***
The parser can buffer (not cache, wrong word ;-) all it wants for performance,
but there must be logic in document.get* to flush the buffer if written content
is pending.

The classic codebase made document.write synchronize with the layout engine by
forcing an inter-thread request/response message handshake after sending written
source through earlier messages.  That change (required by the move of JS/DOM
execution from the main thread to its own thread) did hurt write performance in
4.x compared to 2.x and 3.x quite a bit.

Idea: have all document.get* methods flush the parser's buffer.  Problem: could
there be data in the buffer after the script tag that's calling document.get*,
data that should not yet be flushed through the parser?  Can we reckon the
flush-until point in the buffer?

If the document has loaded, all writes have finished and there can't be anything
in the buffer.  This covers document.get* called from onload or another event
handler whose execution is well-defined only after the doc has loaded fully.

If the document is still loading, the document.get* call could be from an event
handler on an element already loaded, but all bets are off, as far as what
elements are available to such an event handler invocation.

Otherwise, the document.get* call must be from a script tag.  The parser must
already synchronize script tag execution with layout of elements created from
tags that preceded the script tag, in order for DOM 0 document.forms[0], e.g.,
expressions in the script to work when a well-formed form tag entirely precedes
the script's tag.

Since document.write-created content must appear as if loaded in lieu of the
script tag that calls document.write, there must be logic in the parser already
to decide where to "insert" the written data, in case post-script-tag data from
the writing document has been buffered.  In fact, if the original input is:

  <script>
  document.write("<div id=A>ho hum</div>" +
                 "<script>dump('A='+document.getElementById('A'))<\/script>" +
                 "<div id=B>hah</div>");
  dump('outer: A=' + document.getElementById('A'));
  dump('outer: B=' + document.getElementById('B'));
  <script>
  <div id=C>hee</div>

We would like the inner script to find only A, and not B or C.  It's likely that
the C div's source is in the parser's buffer when the write happens.  The write
then inserts (or stacks, saving the outer buffer's read and write cursors if a
buffer stack is used) "<div id=A>ho hum</div><script>...".  When the inner
script runs, div B's source must be buffered too, "before" (or "above" if a
buffer stack is used) the outer (original doc) C div's source.

It seems to me that we need only keep the cursor in the parser's buffer where
the last write stopped inserting or stacking, and flush up to that point on the
next document.get* call.  But I'm writing all this based on deductions, without
looking at source, so I'll shut up now.

/be
Right, buffering, not caching, sorry about that.

Anyway, I now know what's causing this and it's not what I thought at first,
it's not due to the buffering done in the parser, this happens because when the
external script file is loaded the parser is blocked while the external script
is loading and being executed. Since the parser is blocked while the script
executes the document.write()'n data doesn't get parsed untill the script
execution is complete, i.e. after the script calls document.getElementById().

So, how can we fix this? Well, from looking at how the parser deals with
document.write() I think I came up with something that might fix this in an
acceptable way.

When mozilla sees a external script it calls EnableParser(PR_TRUE) on the parser
to block the parser, this call will set mParserEnabled to PR_FALSE in the parser
context. Once that's done mozilla starts downloading the external file and when
the file is downloaded the script file is executed. The external script calls
document.write(), this will create a new parser context that is pushed onto the
parser context stack and when the new context is pushed the enabled state for
the new context is gotten from the old context (i.e. the new context is also
blocked in this case) and this is what needs to change. So my fix is to let the
state of a new context remain enabled even if the context below on the stack is
disabled.

Does this really fix this problem? It appears so, but I need to look into this
more and give it more testing to be sure...

I'll attach a patch and I'd really appreciate comments on this.
Status: NEW → ASSIGNED
Created attachment 21484 [details] [diff] [review]
Don't propagate blocked state to new contexts in the parser
*** Bug 63591 has been marked as a duplicate of this bug. ***
I know next to nothing about the parser, but what you are trying to do sounds
right.  External scripts, like inline scripts, must be able to document.write
HTML that is then laid out as if it were loaded in lieu of the script src= tag.
And any document.get* calls after the write should find the generated element.

/be
Harish and I talked about this and he knows the issues here and was kind enough
to agreed to take this bug off my sholders. We agreed that my patch is atleast
taking us in the right direction but there's still some edge cases were things
could go wrong with the patch. (setting milestone to moz0.8)
Assignee: jst → harishd
Status: ASSIGNED → NEW
OS: other → All
Priority: -- → P3
Hardware: PC → All
Target Milestone: --- → mozilla0.8
(Assignee)

Comment 11

17 years ago
Created attachment 24622 [details] [diff] [review]
Patch v1.1 [ r=jst sr=vidur]
(Assignee)

Comment 12

17 years ago
Fix landed. Marking FIXED.
Status: NEW → RESOLVED
Last Resolved: 17 years ago
Resolution: --- → FIXED
Component: DOM Level 1 → DOM HTML

Comment 13

17 years ago
QA contact Update
QA Contact: janc → desale

Comment 14

17 years ago
Updating QA contact to Shivakiran Tummala.
QA Contact: desale → stummala

Comment 15

17 years ago
verified on Build 2001-06-21-11-0.9.1 (windows 2000)
marking as verified
Status: RESOLVED → VERIFIED

Updated

9 years ago
Component: DOM: HTML → DOM: Core & HTML
QA Contact: stummala → general
You need to log in before you can comment on or make changes to this bug.