Open
Bug 1165237
Opened 10 years ago
Updated 2 years ago
fetch() not aborted upon document unload
Categories
(Core :: DOM: Core & HTML, defect, P3)
Core
DOM: Core & HTML
Tracking
()
NEW
People
(Reporter: robwu, Unassigned)
References
Details
(Whiteboard: [tw-dom] btpp-active)
Attachments
(7 files, 9 obsolete files)
When a document that calls fetch() is unloaded, I expect the request to be canceled and the promise to be rejected. This doesn't happen however.
I have attached a test case that inserts a frame, uses the fetch method of it and then removes the frame. The test case also contains a fetch() call immediately followed by stop(). Both fetches should fail, but they succeed.
In Chrome 42.0.2311.90, both actions lead to cancellation of the request and rejection of the promise. This behavior is not explicitly specified (reported as https://github.com/whatwg/fetch/issues/53) nor explicitly tested (there is a different test that suggests that the intention is that fetch becomes unavailable when the document is gone - https://chromium.googlesource.com/chromium/blink/+/717e1bf242dc8606602db753f509bac13f8423a7/LayoutTests/http/tests/fetch/chromium/discarded-window.html).
Comment 1•10 years ago
|
||
The Fetch should be running with the document's LoadGroup which is the mechanism for signalling cancellation. Ollie, does Stop() cancel the LoadGroup?
Flags: needinfo?(bugs)
Comment 2•10 years ago
|
||
mLoadGroup->Cancel(NS_BINDING_ABORTED); should get called in nsDocLoader::Stop.
Does Fetch deal with Cancel?
Flags: needinfo?(bugs)
Comment 3•10 years ago
|
||
I thought Cancel was automatically handled by the nsIChannel attached to the LoadGroup.
Comment 4•10 years ago
|
||
yes, it is. (but I'm not familiar with Fetch at all, so I don't even know whether it uses some
weird nsIRequests or normal nsIChannels ;) )
Comment 5•10 years ago
|
||
FetchDriver uses NS_NewChannel() with a registered nsIStreamListener. If an error code is passed to OnStopRequest() then the promise is rejected.
Whiteboard: [tw-dom]
Updated•9 years ago
|
Whiteboard: [tw-dom] → [tw-dom] btpp-active
Comment 7•9 years ago
|
||
I am currently working on the Window.stop() case, and I found out that the channel created to serve Window.fetch is created in another runnable, which is executed later. As a result, when we invoke Window.stop, the LoadGroup doesn't aware the existence of the previous fetch and thus cannot reject the fetch request there. I am now planning to find a way to make the channel to be created knows that it shouldn't be created and we should reject the fetch request at that time.
Comment 8•9 years ago
|
||
Sounds like the LoadGroup should have a IsStopped() that the HTTP channel can check. If it's true then just immediately abort instead of starting.
Comment 9•9 years ago
|
||
That won't work, because the loadgroup is per-docshell, not per-document.
And also because if the fetch() call happens _after_ the stop() call then the fetch() should work.
What should be happening here is that something should be getting added to the loadgroup immediately when fetch() is called. You want that anyway, because if you don't do that then the fetch() won't block onload, right? Or are you using the document onload blocker?
Comment 10•9 years ago
|
||
Thanks for the advice from both of you :)
In fact, I don't know whether we should block window.onload when there is a pending fetch request, but I saw some information[1] suggests that it should, and I am trying to find the spec at this moment. If window.onload should be blocked, then I'd like to try Boris's solution, since it might utilize more existing mechanisms, and thus might fix window.stop() case and iframe removal case at the same time. BTW, the iframe removal case is a little trickier, because it seems that the promise returned by the fetch under an iframe won't be resolved or rejected after the iframe is removed.
[1] https://developer.mozilla.org/en/docs/Web/API/GlobalEventHandlers/onload
Comment 11•9 years ago
|
||
I don't think fetch() should block onload unless we do the same for XHR.
Comment 12•9 years ago
|
||
XHR doesn't block onload unless there's a progress listener, iirc.
We could add a thing to the loadgroup that doesn't block onload (but does allow the loadgroup to cancel it).
Comment 13•9 years ago
|
||
In theory fetch() is going to get a progress mechanism in the future as well. I don't think it should block onload.
Comment 14•9 years ago
|
||
That's a separate issue; the XHR behavior is because necko doesn't have a way to tell it "this request should fire progress, but I want it to not block onload", basically...
Comment 15•9 years ago
|
||
Thanks again for your reply.
Let me figure out the way that doesn't block window.onload and make fetch() cancellable, but I think it will take me some time to become familiar with the relationship between a DocShell and a LoadGroup.
Comment 16•9 years ago
|
||
Basically, a docshell owns a loadgroup. All the loads happening in that docshell go into that loadgroup, as do their subresources (though there is some weirdness for SVG resource docs). See also the post I just made to .platform suggesting we should change this, but for now that's the setup.
Comment 17•8 years ago
|
||
Thanks for replying, and I need some help here :(
Currently, I've successfully solve the window.stop case by placing a stub request into the LoadGroup. However, I failed to solve the iframe removal case. Now, I can reject the promise returned by the fetch within an iframe, and its callback is dispatched to the mPromiseMicroTaskQueue of a JSRuntime, but the queue is never processed. Instead, there is another mPromiseMicroTaskQueue of another JSRuntime is processed periodically.
Here is my assumption, there is a JSRuntime for the main script and one for the iframe. The promise returned by the fetch is stored in the mPromiseMicroTaskQueue of the JSRuntime for iframe, and after the iframe is removed, the mPromiseMicroTaskQueue is never visited.
Comment 18•8 years ago
|
||
> Here is my assumption, there is a JSRuntime for the main script and one for the iframe.
There is only one JSRuntime per OS thread. The main script and the iframe share the same JSRuntime.
Comment 19•8 years ago
|
||
Ben, have you had more of a chance to look at this? Are you blocked on someone else?
Flags: needinfo?(bhsu)
Comment 20•8 years ago
|
||
Thank you for spending time watching this issue :)
I've made little progress in this issue recently, since I still trying to figure out what CycleCollectedJSRuntime[0] is, which contains a mPromiseMicroTaskQueue[1]. Since the mPromiseMicroTaskQueue at which we place the promise rejection callback within the iframe is not the same one as the one regularly processed, I am still trying to clarify the relationship between those mPromiseMicroTaskQueues and what JSRuntime is.
Besides, since I think the solutions of the two cases are different, may I ask for your thoughts about whether it's appropriate to separate the iframe removal case into another issue?
[0] https://dxr.mozilla.org/mozilla-central/source/xpcom/base/CycleCollectedJSRuntime.cpp#1157
[1] https://dxr.mozilla.org/mozilla-central/source/dom/promise/Promise.cpp#985
Flags: needinfo?(bhsu)
CycleCollectedJSRuntime is the wrapper class around JSRuntime that Gecko uses. It also implements cycle collection, promise scheduling, and a bunch of other things. It's stored in TLS so we can get the right one depending on whether we're on the main thread or a worker thread.
Comment 22•8 years ago
|
||
Separating out navigation and removal might be ok, but I'd think they're currently handled by the same mechanism...
Comment 23•8 years ago
|
||
Comment 24•8 years ago
|
||
Attachment #8767466 -
Attachment is obsolete: true
Updated•8 years ago
|
Attachment #8767468 -
Attachment description: Bug 1165237 - Part 1: A Promise should still be handled even when it's dying. → Part 1: A Promise should still be handled even when it's dying.
Comment 25•8 years ago
|
||
Comment 26•8 years ago
|
||
After rebasing to the latest Gecko, I found out that the window.stop() case is already solved, so the iframe removal case becomes the only thing we need to deal with in this bug. I should firstly apologize, since I might did something terribly wrong during previous investigation, there seems nothing to do with CycleCollectedJSRuntime things. Instead, to reject the promise return by the fetch within an iframe which is soon removed, we should still handle the promise even when `mGlobal->IsDying()`. However, I am not quite sure whether we should do this in this way. I think I need more input here...
Comment 27•8 years ago
|
||
> we should still handle the promise even when `mGlobal->IsDying()`
The code to not handle it in that situation is there for a reason (check the blame!).
Updated•8 years ago
|
Attachment #8767468 -
Attachment is obsolete: true
Updated•8 years ago
|
Attachment #8767469 -
Attachment is obsolete: true
Comment 28•8 years ago
|
||
Comment 29•8 years ago
|
||
Hi bz,
Thanks to Spider Monkey promise, the testcase provided by Rob now can pass. However, when I tried to conduct a testcase for this bug, I bumped into an even more weird situtation. It seems that promises generated from the promise chain originated from the fetch of a removed iframe cannot be placed into the then(...) of another promise or Promise.all(...). Otherwise, the promise generated from the promise.then(...) or Promise.all(...) remains pending. This case can be reproduced by the new attachment.
Similarly, this case can still be solve by loosening the !global->IsDying() in [1]. According to the discussion in 1058695, we do need this check for preventing infinite promise chains on a dying global object. At this moment, I think a settled promise cannot block other promises in this way, so I am now trying to figure out the relationship between a promise and its global object.
[1] http://searchfox.org/mozilla-central/source/xpcom/base/CycleCollectedJSRuntime.cpp#940
Comment 30•8 years ago
|
||
> Thanks to Spider Monkey promise, the testcase provided by Rob now can pass.
Hmm. What behavior did spidermonkey promise change to make that happen?
> Otherwise, the promise generated from the promise.then(...) or Promise.all(...) remains pending
Sure. A fetch promise can never resolve, really....
That said, we do need a spec for what happens to promises from dead iframes. Please talk to Domenic Denicola about it?
I'm not sure what you're asking at the end there. The point of the IsDying() check is to prevent infinite promise chains from running once we have unloaded the page the promises involved came from. We do that by simply not invoking the callbacks on a promise whose global is going away. This is the basic "script should not keep running in globals which have been navigated away from".
Think about it this way. Say you have a promise chain or Promise.all() and one of the promises in it is waiting for a timer to fire to resolve. The window the timer is set on stops being "loaded" (is navigated away from, or its parent iframe is unhooked from the DOM). That means the timer will never fire and your promise will never resolve. This is fundamentally the same situation as the one you're describing: you're waiting for the result of an async operation, but the UA has silently terminated that async operation...
Comment 31•8 years ago
|
||
Comment 32•8 years ago
|
||
Comment 33•8 years ago
|
||
Hi bz, and sorry for bothering you again.
For the question, it seems that the `MaybeReject()` for SpiderMonkey executes the callback without checking whether `mGlobal` is valid, which the original `MaybeReject()` does in `Promise::Settle()`. To make the behavior the same as before the landing of SpiderMonkey, I've provide a rough patch, which make the fetch promise of a removed iframe never resolve at the first place.
On the other hand, I'd like to talk a little about the testcase I previously provided. The fetch promise of the removed iframe, says `a`, is actually `rejected`, and `a.then(FUNC, ()=>console.log('a'))` works fine, in which case `a` is printed out. Nevertheless, `Promise.resolve().then(() => a).then(FUNC, ()=>console.log('b'))` doesn't print out `b`. I personally think the two cases should act the same, whether both of them are rejected (printed out in this case) or pending. By applying the patch mentioned previously, now they both remain pending and nothing is printed out.
However, Chromium prints out everything in the Testcase. Though we don't have to follow what Chromium does, I'd like to try whether we can make Gecko do the same thing. If I understand Gecko and Spidermonkey correctly, the promise of the removed iframe is actually rejected internally, and the corresponding `PromisJobRunnable` is created and placed into the event queue. Later, when we try to execute the runnable, it's bailed out because of its global object is currently dying [0]. I've been thinking of replacing the global object of the promise and the corresponding `PromisJobRunnable`, since the handle of the promise is in the main script, and IMHO it might be reasonable to set the global to the main window. To do that experiment, I simply mark off the line updating the compartment [2] before creating a function object, and it results in a crash[3]. As the comment [4] indicates, there is a special design for cases with multiple compartment like fetch, and thus changing the behavior of the promise from a removed iframe here needs a more careful and delicate patch, which is impossible with my poor knowledge at this moment :(
After all, I'd like to apply the patch making the promise remains pending, and write a testcase to make sure they are pending. Yet, there will be a difference between Gecko and Chromium here, and we should somehow be prepared for this. Last but not least , thank for your time reading this comment.
[0] http://searchfox.org/mozilla-central/source/xpcom/base/CycleCollectedJSContext.cpp#941-942
[1] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#1316-1322
[2] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#1322
[3] http://searchfox.org/mozilla-central/source/js/src/jscntxtinlines.h#40
[4] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#1316-1320
Flags: needinfo?(bzbarsky)
Comment 34•8 years ago
|
||
> without checking whether `mGlobal` is valid
You mean an IsDying() check? Reinstating that probably makes sense, but we should really have a testcase for this too, so it doesn't regress again... But I'm not sure why the check in PromiseJobRunnable::Run, which was meant to replace it, is not working right. Why isn't it?
> However, Chromium prints out everything in the Testcase.
What does Chromium do with the testcase in bug 1058695 if executed inside the iframe?
> when we try to execute the runnable, it's bailed out because of its global object is currently dying [0]
Right, that's what we added to fix bug 1058695 in the new world. It's not clear to me why it doesn't handle the a.then() case... Which global do we end up observing there: the one for `a` or the one for the callback function?
> IMHO it might be reasonable to set the global to the main window
Which promise job are we talking about, exactly? The fact that "then" is involved suggests we're talking about the callback function being the .then of `a`, which is very much not in the main window.
> and it results in a crash
Sure. Just blindly messing with compartments in this code will do that. ;)
> changing the behavior of the promise from a removed iframe here needs a more careful and delicate patch
Indeed.
> Yet, there will be a difference between Gecko and Chromium here, and we should somehow be prepared for this.
Sure. We need to start by having an actual spec here; there are longstanding spec issues on this...
Flags: needinfo?(bzbarsky)
Comment 35•8 years ago
|
||
Hi bz,
and sorry for the late response.
> You mean an IsDying() check? Reinstating that probably makes sense, but we should really have a testcase for this too, so it doesn't regress again... But I'm not sure why the check in PromiseJobRunnable::Run, which was meant to replace it, is not working right. Why isn't it?
Yes, I meant the IsDying() check. After thinking and understanding the whole picture here, I think the IsDying() check in the PromiseJobRunnable::run() should block the execution here as well. The reason is described in the next section.
> What does Chromium do with the testcase in bug 1058695 if executed inside the iframe?
Sorry for this, I cannot give an explicit answer for it. I've write a testcase(the attachment) for this, in that testcase, I open up an iframe and execute the infinite promise chain. However, the promise chain jams the entire tab, so I cannot remove the iframe via the console. I've try window.setTimeout to remove the iframe automatically, but it seems that the infinite promise chain occupied the entire event loop, and thus chrome doesn't execute the timeout callback at all. I've once tried to use window.setTimeout in the iframe to make the infinite promise chain less aggressive, but it turned out a stupid idea, since we cannot make sure the stop of the infinite promise chain is because of the window.setTimeout of promise job without a valid global object.
> Right, that's what we added to fix bug 1058695 in the new world. It's not clear to me why it doesn't handle the a.then() case... Which global do we end up observing there: the one for `a` or the one for the callback function?
When tracing the global object alone the road, I found out marking [0] out will make this behavior right and come up the following assumption. Given a if the fetch promise of the removed iframe, it seems that the global object of FUNC in a.then(FUNC) is the main html. When processing the then(), the global object of the reactionVal passed in `TriggerPromiseReactions()`[1] is the removed iframe(desired), and the global object of the retrieved `handler`[2] is the main window, which makes me think the handler is FUNC. I am think the FUNC is born this way when the JS interpreter parsing the JS source code, but I still don't find my way out.
[0] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#1162-1178
[1] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#40
[2] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#86
> Which promise job are we talking about, exactly? The fact that "then" is involved suggests we're talking about the callback function being the .then of `a`, which is very much not in the main window.
Hmm, if I understand this correctly, it is by design that the global object of a derived promise is the same as its origin promise, and thus `a` should belong to the main window.
Comment 36•8 years ago
|
||
Comment 37•8 years ago
|
||
(In reply to Ben Hsu [:HoPang] (OOO from 10/26 ~ 10/30) from comment #36)
> Created attachment 8803782 [details]
> Infinite promise chain testcase for chrome
BTW, the testcase cannot be used by Firefox, since Firefox blocks iframe with the same URL as its opener.
Comment 38•8 years ago
|
||
> and sorry for the late response.
It's OK, but it makes it much harder for me to respond in a useful way, since I've completely forgotten all the context here. :(
> However, the promise chain jams the entire tab, so I cannot remove the iframe via the console.
Just have the promise chain itself remove the iframe, after some number of iterations.
> Given a if the fetch promise of the removed iframe, it seems that the global object of FUNC in a.then(FUNC) is the main html
If that's where the function comes from, yes.
> which makes me think the handler is FUNC
Yes.
> it is by design that the global object of a derived promise is the same as its origin promise
Well, the ES spec requires that (modulo people messing with the promise constructor and whatnot), yes.
> and thus `a` should belong to the main window.
I don't follow this part...
Is the basic issue here that while the _promise_ is from the removed iframe the arguments to then() are from the parent page, and hence the check in PromiseJobRunnable::Run tests false, because the function we have there is explicitly created in the same compartment as the argument to then(), due to the bits in EnqueuePromiseReactionJob?
Please talk to Till about this; he's in the middle of rewriting that stuff in EnqueuePromiseReactionJob, as I understand.
Comment 39•8 years ago
|
||
bz's suggestion adopted ;P
Attachment #8803782 -
Attachment is obsolete: true
Comment 40•8 years ago
|
||
(In reply to Boris Zbarsky [:bz] (still a bit busy) from comment #38)
> Just have the promise chain itself remove the iframe, after some number of
> iterations.
Thanks a lot for this, I've never thought an iframe can remove itself! From observing the resource monitor, chrome keep executing the infinite promise chain. (A google chrome helper process keeps consuming 100% CPU)
> I don't follow this part...
Oh, that's an embarrassing typo, I meant `a` belongs to the removed iframe there...
> Is the basic issue here that while the _promise_ is from the removed iframe
> the arguments to then() are from the parent page, and hence the check in
> PromiseJobRunnable::Run tests false, because the function we have there is
> explicitly created in the same compartment as the argument to then(), due to
> the bits in EnqueuePromiseReactionJob?
Yes, IMHO, that's the whole story and the root cause.
> Please talk to Till about this; he's in the middle of rewriting that stuff
> in EnqueuePromiseReactionJob, as I understand.
Sure!
Comment 41•8 years ago
|
||
> From observing the resource monitor, chrome keep executing the infinite promise chain
OK, so that's the bug we were trying to not have. ;)
> Yes, IMHO, that's the whole story and the root cause.
OK. Let's see what till's new setup will look like.
That said, at this point I'm not sure what all the discussion around promises has to do with he original bug as filed and what the current state of things is there....
Comment 42•8 years ago
|
||
Hi Till,
I've bumped into a problem when working with Spider Monkey promise. As the attachment suggesting, a settled foreign promise associated to a dying global object behaves differently when being used differently, where a foreign promise here is a promise generated from another global(e.g., IFRAME.contentWindow.fetch()). Say we have a rejected promise called "a", which is derived from IFRAME.contentWindow.fetch() and the iframe is later removed before the fetch is actually resolved, the following two usage turn out entirely different results, where the result of usage 2 is desired.
1. a.then(null, FUNC); // The PromiseJobRunnable is executed.
2. Promise.resolve(() => a).then(null, FUNC); // The PromiseJobRunnable is bailed out.
The reason usage 2 bails out the PromiseJobRunnable is we find the associated global object is no long alive[0]. After digging code, we found out usage 1 generates the PromiseJobRunnable differently, and hence there are two different results. Belowing is the entire story I came up for this situation.
The promise is created in the iframe and hence its associated global object is the iframe. However, the FUNC is created in the main HTML, and its associated global object is the iframe. As a result, the associated global object of the PromiseJobRunnable created with FUNC (retrieved from the reactionVal of the promise[1]) is the main html, and thus the PromiseJobRunnable is executed. For more detailed information, please refer to comment 35 and comment 38.
Since I'm not very similar to JS engine, and thus have no idea how I can do with this situation. Do you mind handling this situation or giving some advice?
[0] http://searchfox.org/mozilla-central/source/xpcom/base/CycleCollectedJSContext.cpp#942
[1] http://searchfox.org/mozilla-central/source/js/src/builtin/Promise.cpp#86
Flags: needinfo?(till)
Comment 43•8 years ago
|
||
The real question for till is what the relevant code looks like after his refactoring.
The question for _this_ bug is what the spec says to do, what it _should_ say to do, and once we figure out those answers we can think about how to implement that.
Comment 44•8 years ago
|
||
Hi Ben,
as bz says, the more important question is what _should_ happen in this case. Once I know that, I can certainly figure out how to make that happen consistently in the Promise implementation.
Flags: needinfo?(till) → needinfo?(bhsu)
Comment 45•8 years ago
|
||
Thanks to both of you, then I'll be waiting for the result here :)
Flags: needinfo?(bhsu)
Comment 46•8 years ago
|
||
Who's going to figure out what the intended behavior is here? I certainly can't because I'm not familiar with the relevant spec. If you can't either, can you forward the needinfo to someone who can? Thanks!
Flags: needinfo?(bhsu)
Comment 47•8 years ago
|
||
Hi bz,
I am afraid that I need your help again, do you know who I should approach to in mozilla? Since lacking of related knowledge, I don't think I am capable of finding the intended behavior. However, if you think I should directly go to Domenic Denicola, do you mind giving feedback on the three questions below? I am not quite confident that I am asking the right questions. Thanks as always.
**Question 1**
In the design of chromium, does a promise have a corresponding global object?
**Question 2**
Following question 1, if the answer is yes, then which global object do the promises in the code snippet belong to? (The main window or the contentWindow of the iframe.)
```
var p0 = IFRAME.contentWindow.fetch();
var p1 = p0.then(FOO);
var p2 = Promise.resolve().then(() => p0);
```
**Question 3**
Following question 1 as well, if the answer is yes, should we leave an unsettled promise which loses its global object remains "pending" forever and not to execute the callback residing in its `then()`?
Flags: needinfo?(bhsu) → needinfo?(bzbarsky)
Comment 48•8 years ago
|
||
> do you know who I should approach to in mozilla?
Do you really mean "mozilla"?
> However, if you think I should directly go to Domenic Denicola
I think that's a good place to start, at least, because that's who's been working on trying to spec the interaction of promises and the web. Note that he may not know Blinks implementation details around this stuff.
I think the right question to start with is to describe the situation you are trying to address and ask him what he thinks should happen in that situation. That includes both the fetch case and the "infinite loop" case. The "does a promise have a corresponding global?" business is an implementation detail that may become relevant as the observable behavior is defined, but a good start would be sorting out what the observable behavior should be in specific concrete cases....
Flags: needinfo?(bzbarsky)
Comment 49•8 years ago
|
||
> Do you really mean "mozilla"?
Yes, but I am not trying to build a walled garden here, I am just finding a more experienced person to speak for this issue or give me some guidance....
> I think that's a good place to start, at least, because that's who's been
> working on trying to spec the interaction of promises and the web. Note
> that he may not know Blinks implementation details around this stuff.
OK, I'll go find him directly.
> I think the right question to start with is to describe the situation you
> are trying to address and ask him what he thinks should happen in that
> situation. That includes both the fetch case and the "infinite loop" case.
> The "does a promise have a corresponding global?" business is an
> implementation detail that may become relevant as the observable behavior is
> defined, but a good start would be sorting out what the observable behavior
> should be in specific concrete cases....
For the "infinite loop" case, I'll ask whether the promise loop should keep executing even when the "window" is closed or the "iframe" is removed. As to the "fetch" case, I'll ask the whether the pending promise should remains pending forever after the iframe is removed.
Comment 50•8 years ago
|
||
> Yes, but I am not trying to build a walled garden here
I guess I'm not sure what you mean about approaching someone in mozilla. You and are already talking about this... ;)
Your plan for what to ask sounds good.
Comment 51•8 years ago
|
||
Comment 52•8 years ago
|
||
Attachment #8814891 -
Attachment is obsolete: true
Comment 53•8 years ago
|
||
Attachment #8814894 -
Attachment is obsolete: true
Comment 54•8 years ago
|
||
Attachment #8814898 -
Attachment is obsolete: true
Comment 55•8 years ago
|
||
Updated•8 years ago
|
Attachment #8814901 -
Attachment is obsolete: true
Updated•8 years ago
|
Attachment #8814902 -
Attachment is obsolete: true
Comment 56•8 years ago
|
||
Comment 57•8 years ago
|
||
Updated•7 years ago
|
Assignee: bhsu → nobody
Updated•7 years ago
|
Priority: -- → P3
Assignee | ||
Updated•6 years ago
|
Component: DOM → DOM: Core & HTML
Updated•2 years ago
|
Severity: normal → S3
You need to log in
before you can comment on or make changes to this bug.
Description
•