Closed Bug 246699 Opened 20 years ago Closed 17 years ago

CAPS security exceptions should throw richer exception info (not just raw string)

Categories

(Core :: Security: CAPS, defect)

defect
Not set
major

Tracking

()

RESOLVED FIXED
mozilla1.9beta5

People

(Reporter: dmosedale, Assigned: shaver)

References

Details

Attachments

(3 files, 1 obsolete file)

This makes debugging XUL ridiculously hard.  In particular, I had included a
<browser> tag in my remote XUL, and it had hit an error trying to run some JS
inside of the XBL where the browser> tag is implemented.  The only way I figured
this out was by wasting a bunch of time stepping through code in the C++
debugger.  Most XUL developers are likely to give up before they get to that point.

This error showed up in the JS Console:

Error: uncaught exception: Permission denied to get property UnnamedClass.classes

Turns out that JS_EvaluateUCScriptForPrincipals() is being called with the
correct XBL file & line info, but that routine uses
js_ReportUncaughtExceptions(), which doesn't have access to the file and line info.
Attached file simple test case (deleted) —
The attached XUL test case, when run from a file: URL, has permission issues. 
The errors that appear in the JS console are as quoted above.

*** This bug has been marked as a duplicate of 243869 ***
Status: NEW → RESOLVED
Closed: 20 years ago
Resolution: --- → DUPLICATE
No, this isn't a dup of that -- in this case, there's no error report being
generated, just a string jsval being thrown directly.

It's almost a dup of bug 244843, except that 244843 proposes finding a way to
throw nsScriptError or something.  I tried having the caps code -- wherein the
bug really lies -- call JS_ReportErrorNumberUC, but didn't have a lot of joy. 
I'll try more later.
Status: RESOLVED → REOPENED
Component: JavaScript Engine → Security: CAPS
Resolution: DUPLICATE → ---
Summary: XBL property setter/getter exceptions report no helpful debugging info → CAPS security exceptions should throw richer exception info (not just raw string)
Taking the liberty of assigning to caillon -- shaver, if you wanted this, grab it.

/be
Assignee: general → caillon
Status: REOPENED → NEW
I'll take it if he doesn't want it, but otherwise I'll lay low until I have a
patch.  We could actually replace a lot of that string-munging nonsense in the
caps code with a custom errorCallback, too, if people are set up to localize
js.msg bits when localizing Mozilla.
shaver: beware bug 215173 -- feel free to take it first and fix it, so you can
use custom .msg files.

/be
See also bug 207273.  I probably won't have time to fix this in the forseeable
future, so feel free to steal it, shaver.
Blocks: 302705
Depends on: 215173
Attached patch JS_ReportError seems to work (obsolete) (deleted) — — Splinter Review
js> q
function q() {
    Cc = Components.classes;
    Ci = Components.interfaces;
    caps = Cc['@mozilla.org/scriptsecuritymanager;1'].getService(Ci.nsIScriptSecurityManager);
    ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
    a = ios.newURI("http://a", null, null);
    b = ios.newURI("http://b", null, null);
    caps.checkSameOriginURI(a, b, 1);
}

js> try { q() } catch (e) { f = e }
Error: Security Error: Content at http://a/ may not load data from http://b/.
js> for (a in f) print(a)
message
fileName
lineNumber
stack
name
js> f.fileName
typein
js> f.stack
checkSameOriginURI([object XPCWrappedNative_NoHelper],[object XPCWrappedNative_NoHelper],1)@:0
q()@typein:7
@typein:14

Note: this is using windbg to force cx, because cx isn't available to the js in xpcshell for failing. I'm not entirely pleased that checkSameOriginURI appears in the stack, but I think it's ok.
Assignee: caillon → timeless
Status: NEW → ASSIGNED
Attachment #308594 - Flags: review?(caillon)
Comment on attachment 308594 [details] [diff] [review]
JS_ReportError seems to work

r+a=shaver, conditional on filing a bug to track needing the ability to do multi-principal testing in xpcshell, marked as blocking this.  Thanks!
Attachment #308594 - Flags: review?(caillon)
Attachment #308594 - Flags: review+
Attachment #308594 - Flags: approval1.9+
Depends on: 422191
Comment on attachment 308594 [details] [diff] [review]
JS_ReportError seems to work

mozilla/caps/src/nsScriptSecurityManager.cpp 	1.347
Status: ASSIGNED → RESOLVED
Closed: 20 years ago17 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla1.9beta5
This seems to work. Now 

function q() {
    Cc = Components.classes;
    Ci = Components.interfaces;
    caps =
Cc['@mozilla.org/scriptsecuritymanager;1'].getService(Ci.nsIScriptSecurityManager);
    ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
    a = ios.newURI("http://a", null, null);
    b = ios.newURI("http://b", null, null);
    caps.checkSameOriginURI(a, b, 1);
}

try { q() } catch (e) { alert(uneval(e)+"\n"+e.stack); } 

when executed as a part of some html gives the alert that reads:

(new Error("Permission denied to get property XPCComponents.classes", "file:///home/igor/s/x.html", 7))
classes()@:0
q()@file:///home/igor/s/x.html:7
@file:///home/igor/s/x.html:17

where line 7 is Components.classes.
thanks :), marking verified. For the curious 

javascript:function q() {Cc = Components.classes;} try { q() } catch (e) { prompt('',uneval(e)+"\n"+e.stack); } 

used to yield:
"Permission denied to get property XPCComponents.classes" undefined

whereas:
(new TypeError("null has no properties", "javascript:function q() {null.null;} try { q() } catch (e) { prompt('',uneval(e)+\"\\n\"+e.stack); }", 1)) q()@javascript:function q() {null.null;} try { q() } catch (e) { prompt('',uneval(e)+"\n"+e.stack); }:1 @javascript:function q() {null.null;} try { q() } catch (e) { prompt('',uneval(e)+"\n"+e.stack); }:1
Status: RESOLVED → VERIFIED
So, a test would be something like this:

function test(url) {
    var box = Components.utils.Sandbox(url);
    function boxedFunction() {
        try {
            Components.classes;
        } catch (e if e instanceof Error) {
            return e.stack;
        } catch (e) {
            return "No Stack";
        }
        return "No Error";
    }

    var result = Components.utils.evalInSandbox(uneval(boxedFunction) + ";\n boxedFunction();", box);
    print(result);
}
test("http://example.com")

anyway, some helpful soul should convert that into a test (note that xpcshell doesn't have a way to emulate chrome w/ a sandbox)
Blocks: 422767
Ok, I tried b5pre and I'm a bit confused by the result.  Yes, thanks now I do get a file and line number, but its 
[message]=Permission denied to create wrapper for object of class UnnamedClass;
[fileName]=XPCSafeJSObjectWrapper.cpp;
[lineNumber]=445;
What do I do with this information?
Ok, I tried b5pre and I'm a bit confused by the result.  Yes, thanks now I do get a file and line number, but its 
[message]=Permission denied to create wrapper for object of class UnnamedClass;
[fileName]=XPCSafeJSObjectWrapper.cpp;
[lineNumber]=445;
What do I do with this information?
Comment on attachment 308594 [details] [diff] [review]
JS_ReportError seems to work

So the NS_ERROR_DOM_BAD_URI -> NS_OK nicely breaks what those methods actually should do.
It does?  The test snippets here from timeless and igor seem to show it working, but perhaps native callers expect otherwise.  Can you give a test case to show the bustage?

Timeless: where's the bug for the test infrastructure?  You should cite it here.
NS_ERROR_DOM_BAD_URI -> NS_OK caused bug 423375, which has testcases.
Backed out due to bug 423375.
mozilla/caps/src/nsScriptSecurityManager.cpp 	1.348 
Status: VERIFIED → REOPENED
Resolution: FIXED → ---
Please get one of me, jst, mrbkap, brendan, or dveditz to review any followup changes to this code?
Based on work with Chromebug/Firebug it appears to me that messages like

Error: uncaught exception: Permission denied to get property UnnamedClass.classes

appear in the consoleService without passing through jsdIDebuggerService.onError(). This means that I cannot do things like break into the debugger to the point of error, track the throw/catch chain, check the call stack, and in future do deeper analysis on the error.

If you don't want to fix this as part of this bug I will open a new one.
That's the case (because we throw, rather than reporting an error) and this bug's patch will fix it once I get through revving it and getting the tests in place.  There is still hope for b5!

timeless: taking, hope that's OK!
Assignee: timeless → shaver
Status: REOPENED → NEW
The test fails on a build with the prior patch in it, and succeeds on one without out.  When it fails it reports 3 failures, which is a little odd, but I think just related to the way error reporting is done.

Much obliged if I could be schooled on mochinits as well as getting mrbkap's r+ to land the tests.
Attachment #310594 - Flags: superreview+
Attachment #310594 - Flags: review?(mrbkap)
Attachment #310594 - Flags: review+
Comment on attachment 310594 [details] [diff] [review]
add test for regression the earlier patch caused

Looking for not-me approval to land our first even caps mochitest, so we're safer when I fix this bug in an hour.
Attachment #310594 - Flags: approval1.9?
Attachment #310594 - Flags: approval1.9? → approval1.9+
Like the previous patch, only with tests and without turning off all our security checks. :)

With this, the original test case gives more descriptive error messages, though they are still somewhat confusing:

Security Error: Content at chrome://global/content/bindings/browser.xml may not load data from https://bugzilla.mozilla.org/attachment.cgi?id=150743.
Error: Permission denied to get property XPCComponents.classes
Source File: chrome://global/content/bindings/browser.xml
Line: 687
Security Error: Content at chrome://global/content/bindings/browser.xml may not load data from https://bugzilla.mozilla.org/attachment.cgi?id=150743.Error: Permission denied to get property XPCComponents.utils
Source File: chrome://global/content/bindings/browser.xml
Line: 650

Are subject/object backwards there?  Is that scary?  Questions for later!

I bet there are more places that just set a string exception, and I will hunt for them after I get this in, possibly asking for beta5 permission to try to make things easier on extension developers updating their stuff.
Attachment #308594 - Attachment is obsolete: true
Attachment #310617 - Flags: review?
Attachment #310617 - Flags: approval1.9?
Comment on attachment 310617 [details] [diff] [review]
report stacks instead of strings, and test that we do in at least two cases

(frickin' tenser...)
Attachment #310617 - Flags: review? → review?(jst)
Tests never need approval.
QA Contact: pschwartau → caps
Attachment #310617 - Flags: superreview+
Attachment #310617 - Flags: review?(jst)
Attachment #310617 - Flags: review+
Comment on attachment 310617 [details] [diff] [review]
report stacks instead of strings, and test that we do in at least two cases

a=mconnor on behalf of 1.9 drivers
Attachment #310617 - Flags: approval1.9? → approval1.9+
Landed with test.
Status: NEW → RESOLVED
Closed: 17 years ago17 years ago
Resolution: --- → FIXED
> Are subject/object backwards there?

No.  What we have here is the XBL-cloned method file trying to touch Components.  The principal is that of the bugzilla attachment, but the script filename is of course the chrome:// URI.  The access is not allowed, so there is a security exception thrown.  That's the "Error:" part there.

Now before we fire the onerror event, we do a check to make sure that's OK.  It's a check on URIs, not principals, because JS exceptions don't carry principals.  See the code and XXX comments in NS_ScriptErrorReporter.  The check performed here is a symmetric CheckSameOriginURI() check, so it doesn't matter that the erroring script's URI is passed in first.  The verbiage of the error it reports is somewhat bogus.  Ideally, we would in fact be checking whether the window principal there can access the erroring script's principal...
(In reply to comment #31)
> > Are subject/object backwards there?
> 
> No.  What we have here is the XBL-cloned method file trying to touch
> Components.  The principal is that of the bugzilla attachment, but the script
> filename is of course the chrome:// URI.  The access is not allowed, so there
> is a security exception thrown.  That's the "Error:" part there.

I'm with Mike: the message is backwards at least as far as I understand your explanation. 
"Security Error: Content at chrome://global/content/bindings/browser.xml may not" --- this clearly say the bad guy is browser.xml. As an extension developer my immediate reaction will be "browser is broken", because a core URL is in an error message as the subject.

"load data from https://bugzilla.mozilla.org/attachment.cgi?id=150743."
--- this confirms it, the browser can't load a simple URL.

"Error: Permission denied to get property XPCComponents.classes", well this must be bogus info caused by the broken browser, since it clearly has nothing to do with the rest of the message.

I'm not saying extension developers should come to these conclusions, I am only predicting that they will. And you'll spend the next two years answering newsgroup postings on the subject.

I gather that the https://bugzilla principal created a widget from bindings/browser.xml and that widget tried to access XPCComponents.classes, which is not allowed for https://bugzilla?  But the error generator does not know from widgets, so it can't give this kind of explanation? 
> I'm with Mike

As in you think there's a security hole?  That's what he was being worried about, if I read his comment correctly.  I agree that the error verbiage here is quite suboptimal.

> "load data from https://bugzilla.mozilla.org/attachment.cgi?id=150743."

Like I said, the verbiage of this is bogus.  If someone cares, they can flip the two URIs in that security check, but there is no loading going on here at all.  It's just totally the wrong thing to be reporting.  Worth a bug if there isn't one on this already.  The only case this happens in is if an exception is thrown by script from a URI which has origin A but is running against a window from a URI which has origin B.  The right thing to do here, imo, would be to not have the security manager report anything, and have the caller (which knows what's actually going on) report the situation.  Or just make the whole thing silent; there's no pressing need to report that we're leaving information off the onerror event, in my opinion.

> And you'll spend the next two years answering newsgroup postings on the
> subject.

Of course that doesn't have anything to do with this bug, since the text was the same before the patches here...

> I gather that the https://bugzilla principal created a widget from
> bindings/browser.xml and that widget tried to access XPCComponents.classes,
> which is not allowed for https://bugzilla?

That's correct.

> But the error generator does not know from widgets, so it can't give this
> kind of explanation? 

For the XPCComponents.classes error, correct.  The right thing to do may be to log not just the location of the script but also the principal that failed the security check...  Might also be worth a separate bug.
(My original commit bounced off of not having the checkin-and-backout of Timeless' original patch, so it just actually landed now.  Should be in tomorrow's nightly nonetheless.)

I'm actually a little surprised that content is allowed to clone a chrome:-loaded binding that way, given that we don't permit content to load the file directly.

This bug is about not having the security exception be a raw string.  I agree that we would like to have more perfect error messages in these strange cases, but they need other bugs so that they can be prioritized effectively.  (Though if people decide "man, loading XUL from content is just so strange and hard I think I won't do it", I will consider that a wonderful thing.)

The "permission denied to get property" message's lack of context wants its own bug to ease tracking in this precious endgame time, and I have filed bug 424066 for such purposes.
In 
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9b5pre) Gecko/2008032106 Minefield/3.0b5pr
I get an error stack, but the top of the stack is incorrect, it is 
XPCSafeJSObjectWrapper.cpp.  The next frame is in my JS code. 

Even if I ignore the first frame I don't know what to do next. I don't see how a fix for 424066 will help me since the message I get has nothing about "may not load data from". My message is "Permission denied to create wrapper for object of class UnnamedClass" but I am not asking for a wrapper and I have no class named UnnamedClass and neither of these occur at the line reference in the stack....
See comment 34 paragraph 3.
(In reply to comment #34)
..
> This bug is about not having the security exception be a raw string.  I agree

I don't agree with this interpretation.  The reporter has exactly the problem I have: an error whose cause cannot be determined. One part of the solution is to have the exception reported correctly. The rest of the solution is to have the report mean something.

> that we would like to have more perfect error messages in these strange cases,
> but they need other bugs so that they can be prioritized effectively.  (Though

Ok so how can vote for finishing this job? 

> if people decide "man, loading XUL from content is just so strange and hard I
> think I won't do it", I will consider that a wonderful thing.)

? What does this have to do with UnnamedClass?


> The "permission denied to get property" message's lack of context wants its own
> bug to ease tracking in this precious endgame time, and I have filed bug 424066
> for such purposes.

Which is nice, except that bug report is not about "Permission denied to get property UnnamedClass.classes" which is the problem that I have.


(In reply to comment #37)
> (In reply to comment #34)
> ..
> > This bug is about not having the security exception be a raw string.  I agree
> 
> I don't agree with this interpretation.  The reporter has exactly the problem I
> have: an error whose cause cannot be determined.

We can disagree, then; I think "not just raw string" in the summary is pretty clear, and I believe that "throw a real exception with location information" was what Dan and I were talking about when he originally filed this bug.

> > that we would like to have more perfect error messages in these strange cases,
> > but they need other bugs so that they can be prioritized effectively.  (Though
> 
> Ok so how can vote for finishing this job? 

Most effectively with test cases (attached to bugs, so people who are going to fix them can reproduce them clearly and directly, and so that they can be turned into regression tests).  Stack traces can help isolate the pattern that's relevant, and I'm happy to help people decode their error-reporter stack trace.

> > if people decide "man, loading XUL from content is just so strange and hard I
> > think I won't do it", I will consider that a wonderful thing.)
> 
> ? What does this have to do with UnnamedClass?

It has to do with the value of making unprivileged-XUL debugging easier, relative to other opportunities competing for the same cost.

> Which is nice, except that bug report is not about "Permission denied to get
> property UnnamedClass.classes" which is the problem that I have.

Indeed, it's not; my bad, I was clearly quite confused.  Can you file another with a test case, and cite it here?
(In reply to comment #38)
> Indeed, it's not; my bad, I was clearly quite confused.  Can you file another
> with a test case, and cite it here?

No, and that is exactly why I keep coming back here: I can't create a test case because I have no clue what causes the messages. 
If you can't narrow down a regression range with nightlies, or add tracing to your code to see how far it gets, or temporarily disable chunks of code to help narrow it down, or stack traces with windbg/a debug build/etc. then we'll have to wait until someone else can do those things.  I was hoping you could help shorten the cycle here by doing some of the background research, since you're much more familiar with Firebug's internals than I am, and the bulk of figuring out these cases has been "what is Firebug try to do here, with these withs and __scopes__?", at least for me. :(
The reason that the Firebug command line has been difficult to debug is the poor quality of error reporting from the mozilla engine. By systematically lowering the priority of error handling, as you are doing in this case, the cumulative effect is a large number of hours spent by lots of different people trying to understand problems in other people's code. Concretely, in the case of __scope__, the error message pointed to the wrong line and it had no embedded context information (ie "principal http://foo.com not allowed to execute code from principal chrome://..."). With such information the command line issue would not have needed a windbg build to sort out.  I know that everyone wants to ship FF3, but the effect of these choices is to push out the time when the platform works, even if it ships.
(In reply to comment #40)
> If you can't narrow down a regression range with nightlies, or add tracing to
> your code to see how far it gets, or temporarily disable chunks of code to help
> narrow it down, or stack traces with windbg/a debug build/etc. then we'll have
> to wait until someone else can do those things.  

The day this problem started, the lines that it points to, and my efforts to temporarily disable chunks of code are available here:
https://bugzilla.mozilla.org/show_bug.cgi?id=422767
(In reply to comment #41)
> By systematically
> lowering the priority of error handling, as you are doing in this case

That is categorically _not_ what I'm doing.  Someone has to do the narrowing and isolation work, regardless of priority.  If you can't or won't, then someone else has to, and because I believe that you are able to do that narrowing and isolation work better and faster than I am, I believe that we will get to a fixed state faster if you do that part, and then I or someone else take over to look at the internals once we know better what we're looking for.

I'm trying to get it fixed faster, with your help, but I will work on it whether you help with isolation or not.
(In reply to comment #41)
> With such information the command
> line issue would not have needed a windbg build to sort out.

I should add: you don't need a special build for this, just windbg or visual studio, and the configuration outlined in http://developer.mozilla.org/en/docs/Using_the_Mozilla_symbol_server -- we should advertise that better. :/
(In reply to comment #1)
> Created an attachment (id=150743) [details]
> simple test case
> 
> The attached XUL test case, when run from a file: URL, has permission issues. 

As of Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9b5pre) Gecko/2008032505 Minefield/3.0b5pre
the testcase in comment 1 now gives an informational warning in jsconsole:

Security Error: Content at chrome://global/content/bindings/browser.xml may not load data from https://bugzilla.mozilla.org/attachment.cgi?id=150743.

And an error message

Error: Permission denied to get property XPCComponents.utils
Source File: chrome://global/content/bindings/browser.xml
Line: 650

What would you do with this information? I believe it would be very tempting to conclude that the browser is broken: it has failed on line 650, that is what the error message says.


Nevertheless this is a huge improvement over the original result.
Also as of
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9b5pre)
Gecko/2008032505 Minefield/3.0b5pre
my extension no longer emits the messages
Permission denied to get property UnnamedClass.classes

I do see new messages from different areas of code,
Permission denied to get property History.previous
and the stack on these messages is incorrect:
<top>
(0)XPCSafeJSObjectWrapper.cpp:445-446@445
(0)chrome://firebug/content/reps.js:456-500@470
(0)chrome://firebug/content/domplate.js:236-237@236
(0)chrome://firebug/content/domplate.js:173-186@182
...
That would be tempting, indeed; I sure thought it was broken for a while.

We should be reporting the principal's URI there, I think.

The info message in jsconsole is bug 424066, waiting for approval to land the patch that will just make it go away.
Yes, I roll my eyes at the C++ filename there a little bit.  What's the source look like for the other parts of the stack?
> and the stack on these messages is incorrect:
> <top>
> (0)XPCSafeJSObjectWrapper.cpp:445-446@445
> (0)chrome://firebug/content/reps.js:456-500@470

You mean reps.js line 470 is not trying to get History.previous?  Or is your issue the wrapper function that sits between that code and the "real" History.previous?

> We should be reporting the principal's URI there, I think.

We need to report both.  The line number goes with the script filename.  The permission was denied to the principal URI.  If you meant that we should add the principal URI to the permission denied message (at least when it differs from the script filename, if not always), I agree 100%.
The rest of the stack is great, rep.js at line 470 is
                    val = object[name];
Which together with the message
Permission denied to get property History.previous
leads me to assume that object = History and name = previous and that, for some reason unfathomable, some clever developer made a pseudo Javascript object that has properties enumerable and settable but not readable. 

The stack is "incorrect" from my point of view because it has C++ code referenced. Naturally error handling code like debuggers will assume the problem is at the top of the stack.  

I'm puzzled by 
>Or is your
>issue the wrapper function that sits between that code and the "real"
>History.previous?
I can't see in Javascript any wrappers; is there a way to tell in Javascript if the object is wrapped or not?
In this case |object| is an XPCSafeJSObjectWrapper object wrapped around a History object.  XPCSafeJSObjectWrapper has a getter for the "previous" property that simply calls the History object's getter for that same property.  The purpose of this getter is to make sure that the correct principal is in scope when the History object's getter is called.  This getter is generated completely inside C++ by calling CompileFunction() on a string stored in memory and hence doesn't have any JavaScript file or line associated with it.  It uses the C++ filename and line number for lack of anything better.  It's not clear what else it could use.  It _might_ be able to use the filename/lineno of whatever script is running on cx, I suppose, but that assumes the property access is being performed by that script.  That would be the common case, but it's not necessarily true.

XPCSafeJSObjectWrapper is designed to be transparent to JavaScript except for the security-check wrapping, so I don't think you can detect its presence from JS code...
In any case, all this is very far afield from the original purpose of this bug.  It would work a lot better to have this sort of conversation in the newsgroups, I suspect.
Here's one with the .cpp in the middle of the stack.  Its growing on me: revealing this amount of inner workings could be useful. Can I count on it being around for while?

<top>
(0)chrome://firebug/content/trace.js:51-112@91
(0)chrome://firebug/content/consoleInjector.js:50-56@55
(0)XPCSafeJSObjectWrapper.cpp:446-447@446
(0)chrome://firebug/content/commandLine.js:79-92@91
(0)chrome://firebug/content/commandLine.js:109-111@110
(0)http://localhost:63636/EclipseWorkspace/FireclipseExtensions/firebug/tests/console/:5-14@10
<bottom>
The .cpp is just there to mediate certain cross-origin calls and make sure that the caller is not exploitable by the callee.  It can easily occur in the middle of the stack, or multiple times, etc.

> Can I count on it being around for while?

You can count on it being there in 1.9.x builds, I think.  Past that, I can't guarantee anything.
I'd like to look at making that stack entry have information about the principals in play, so that it's more useful, but yeah I think it's there for the 1.9.x cycle at least.  If it's useful to you, then it should definitely stay, or have an equivalent added if we need to change it in moz2. :)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: