Open Bug 1378459 (webextensions-startup) Opened 7 years ago Updated 2 years ago

[meta] Allow some addon functionality to load prior to any content loading

Categories

(WebExtensions :: General, enhancement, P3)

enhancement

Tracking

(Not tracked)

People

(Reporter: andy+bugzilla, Unassigned)

References

(Depends on 1 open bug)

Details

(Keywords: meta)

Attachments

(3 files, 2 obsolete files)

Some extensions might want to block on startup to ensure that they load on the first page load in Firefox. Currently they start on startup, but don't block meaning that the background page may or may not have loaded before the page is rendered. In testing locally with Windows and uBlock Origin on Nightly it usually renders the first page with the extension loaded. However other people are saying otherwise. We should probably do this through a permission so it can prompt users about how this can degrade start up performance. This should probably depend upon out of process extensions? Would also be curious if Chrome provides this guarantee. Assuming Rob Wu might know the answer to that question.
Instead of adding a separate permission for this, I think we should just do it for extensions that use webRequestBlocking (and possibly others like non-blocking webRequest)
(In reply to Andrew Swan [:aswan] from comment #4) > Instead of adding a separate permission for this, I think we should just do > it for extensions that use webRequestBlocking (and possibly others like > non-blocking webRequest) I'd rather not conflate webRequestBlocking with blocking startup to allow the load of the background script.
(In reply to Shane Caraveo (:mixedpuppy) from comment #5) > (In reply to Andrew Swan [:aswan] from comment #4) > > Instead of adding a separate permission for this, I think we should just do > > it for extensions that use webRequestBlocking (and possibly others like > > non-blocking webRequest) > > I'd rather not conflate webRequestBlocking with blocking startup to allow > the load of the background script. "conflate" implies that they aren't related. but under what circumstances would an extension use webRequestBlocking but not actually care if it has the opportunity to examine every outgoing request?
I don't think they are related. There may be non-webrequest reasons an addon needs early loading of its background page. I wont pretend to have specific use cases, it just hits me wrong to use webrequestblocking.
This bug mentions background pages, but it could also be a content script that is loaded at document_start, which I think bug 1332273 comment 0 also affects. Perhaps its just a generic "whole add-on blocks" as opposed to the background?
> the background page may or may not have loaded before the page is rendered Regarding blockers, my understanding is that the real issue is being able to load all filter rules before a page is rendered, this is especially true for blockers needing to load/parse/instantiate 10Ks of rules. For uBO, my approach is to pre-compile filter lists in order to remove the cost of parsing at load time. So far this seems to work rather well. If this still fails, there is also an experimental setting[1] which allows uBO to block all network requests before its filter rules are all loaded in memory, and as far as I can tell, it takes care of such issue if ever it occurs on some slower systems.[2] [1] https://github.com/gorhill/uBlock/wiki/Advanced-settings#suspendtabsuntilready-experimental [2] https://github.com/gorhill/uBlock/issues/1327
Chrome does not provide a guarantee that extensions are loaded when a browser is started*. Even for extension pages (starting Chrome with a chrome-extension://[extensionid]/page.html), I have seen the behavior change multiple times in the past, from randomly not having an extension-privileged script context available, to having partially broken API bindings. * actually, that is not completely true. Chrome does (or did?) wait for user scripts to have loaded before loading web pages. There is more context in this bug from 2015 (still not "fixed"): https://crbug.com/523634 ("Resource loading should be deferred until webRequest extensions have finished loading"). In Chrome, background pages don't have to be around in order for an extension to function well (they can be transient, in the form of event pages). Ideally we should also go in this direction, that extensions don't depend on the existence of a background page to work. Chrome's way of offering a good UX with reasonable performance is to allow extensions to declaratively register rules that may be relevant before an extension has started. This includes: - The contextMenus API (registered menu items persist across restarts). - declarativeWebRequest API (webRequest handlers that don't require a roundtrip to an extension script, persists across restarts) (this API is restricted to non-release channels due to the lack of engineering efforts, but it is being picked up again - https://crbug.com/696822). - Content scripts in manifest files (apparently? Not documented except for a comment in https://crbug.com/523634#c4). If any, we should put this behind a NEW permission, and require manual approval by reviewers on AMO before allowing an extension to block startup. If devs submit add-ons and often get rejected without suitable alternatives for their use case, then we can consider adding APIs to help these add-ons to provide their functionality without blocking startup.
(In reply to Andy McKay [:andym] from comment #0) > Some extensions might want to block on startup to ensure that they load on > the first page load in Firefox. So by "block on startup" I assume (hope) you mean "block content process startup"; this shouldn't block first paint of the browser chrome. And if the content process creation stays blocked for a user-noticeable amount of time after we have painted the chrome, maybe we should display a warning in the content area saying which extension is to blame for this delay.
(In reply to Shane Caraveo (:mixedpuppy) from comment #7) > I don't think they are related. There may be non-webrequest reasons an > addon needs early loading of its background page. I wont pretend to have > specific use cases, it just hits me wrong to use webrequestblocking. The longer version of what I was trying to suggest is that we should be able to figure out when an extension needs to be loaded based on the apis it uses, rather than making extension authors declare it explicitly. Documenting how an extension author should decide whether to request that permission will be tricky and no matter how well we document it, many will still get it wrong. And punting to AMO reviewers also sounds like a bad idea. As to Florian's questions, extensions shouldn't block the first chrome paint but extensions that examine network requests obviously need to block any content loads.
(In reply to Rob Wu [:robwu] from comment #10) > If any, we should put this behind a NEW permission, and require manual > approval by reviewers on AMO before allowing an extension to block startup. Manual review is not a sustainable path forward. We are either comfortable with the API being used, or we don't allow it.
(In reply to Andrew Swan [:aswan] from comment #12) > (In reply to Shane Caraveo (:mixedpuppy) from comment #7) > > I don't think they are related. There may be non-webrequest reasons an > > addon needs early loading of its background page. I wont pretend to have > > specific use cases, it just hits me wrong to use webrequestblocking. > > The longer version of what I was trying to suggest is that we should be able > to figure out (In reply to Andrew Swan [:aswan] from comment #12) > The longer version of what I was trying to suggest is that we should be able > to figure out when an extension needs to be loaded based on the apis it > uses As an exercise, what does that look like? I could presume that an extension not using webrequest or content scripts but has a background script does not need to block. A content script matching any tab loading on startup needs to block. A webRequestBlocking permission with a background script and/or content script may or may not need to block, and it's hard to know since the listeners are runtime. Are there other cases? > but extensions that examine network requests obviously need to block any > content loads. I don't think it's obvious at all. For instance, a webRequestBlocking addon may only be listening on certain domains, even if <all_urls> is requested. Those domains may not be in any tab on startup. I guess we could punt on that and block startup if "webRequestBlocking && <all_urls> && background || content script" is in the manifest. I cant imagine an addon with webRequest having no background or content script, but you never know (perhaps a devtools addon). Of course, by "block startup" we're talking about loading those addons prior to content loads, not a general blocking of startup.
Summary: Allow some background pages to block start up → Allow some addon functionality to load prior to any content loading
Priority: -- → P2
No longer blocks: webext-oop
Andy, why do you think that 1380312 is a duplicate? 1) That bug was introduced on Firefox 54 and can be reproduced on macOS version only. 2) It's not related to loading of extension's background page - background page is loaded as usual and that part of any extension is working fine. 3) Problem described in 1380312 somehow related to initialising first Firefox window. On second window all extensions work fine again. So it's not related to pages loading process. As far as I know, Firefox 54 introduced "multiprocess" feature. And it looks like, that the cause of 1380312 is a data race that occurs on initialisation of main process and first gui window. I think 1380312 should not be in resolved state yet.
Hi all, Any progress on this? I think we are over analyzing it by trying to decide exactly which extensions should be loaded before the first web request, and which ones can wait. Maybe to start we should just load them all before web requests, and maybe find reasons to delay some of them later on. Doing them all up front will at least get us back to the level of reliability provided by the older extension APIs. If we expose an API that lets an extension block web requests, we shouldn't make it impossible for that extension to do it reliably, just to possibly save a few milliseconds for the very first page load.
Bug #1396270 got marked as a duplicate of this issue, but I reported the issue because it isn't just that add-ons aren't loading before the first web request (as mentioned here); it's that they don't load -at all-, not before, not after. I understand that the discussions are related, but there has to be some mechanism to make sure that they are loaded at some point of the boot-up. In terms of this bug, I imagine it would be most pertinent to inform the user of the loading process with some sort of icon or dialog, i.e., either allow the user to wait for the add-ons to load properly, or, alternative, allow him or her to skip the process and start viewing content right away. Educating Android users of the problems of the ecosystem seems to me like the right thing to do. Since the bug is now closed, I will note here that using only WebExtensions addons in Nightly since a week ago or so, the issues I was having with Firefox properly loading the add-ons have lessened considerably. They seem to boot up more reliably.
> I wont pretend to have specific use cases, it just hits me wrong to use webrequestblocking. Another use-case: proxyAPI URLs are loading before an addon's background.js (where it calls proxy.register()) has completed. Those URLs are not loading through a proxy server, which causes privacy risks.
Hi Andy, is this something that will happen in 57 or 58? If this is planned for 57, we should talk on the timeline of uplifting this to beta57.
Flags: needinfo?(amckay)
This is too big and risky to land for 57 along with the problem that it will negatively affect Firefox startup performance. It's on our roadmap, but not sure of the timeline.
Flags: needinfo?(amckay)
Thanks, I'll mark this a wontfix for 57 then.
Dusting this off again and thinking a bit about some comments Kris made earlier, I don't believe that getting a background page fully loaded during startup is a sensible thing. Although its more of a piecemeal effort, I think we should handle this separately for individual APIs. webRequest is the big one here but I think it isn't conceptually that difficult to handle: we can stash any event listeners and their corresponding filters in addonStartup.json and then during browser startup, install appropriate observers to block any affected network loads until the extension has started up and initialized its listeners. This works for what I assume is the common case (though I have nothing to back that up) where an extension always creates the same webRequest listeners. Perhaps it would make sense to try to distinguish listeners created when the extension is starting up from listeners created later (and only apply this technique to the first type). Regardless, we still have to deal with the scenario where an extension doesn't re-create a listener but we are blocking some outgoing requests. I don't have a great idea other than using some heuristics to decide when to give up, I would love to hear suggestions. That leaves the proxy API and probably a bunch of others unaccounted for as well as things like bug 1388270 and that's kind of a bummer, but overall I think its the only feasible option we have. Unless somebody has a strong objection (and more importantly a workable counter-proposal) I'll do the necessary bugzilla shuffling so we can have a bug for the webRequest work outlined above (which I'll then take a stab at implementing) and a separate one for proxy. ni to Kris and Shane since their specific feedback would be especially useful.
Flags: needinfo?(mixedpuppy)
Flags: needinfo?(kmaglione+bmo)
It would be great to capture Kris' comments here so we know the issues brought up. I don't really see that as a reliable approach, there are way too many variables to make sure this works right. Having proxy not work isn't a bummer, it's a problem that may cause privacy and security issues for users. If it's possible to pause all outbound network requests, having a permission in the manifest to mark an extension as blocking startup (or blocking network I guess) could work. We just block everything until that set of extensions are loaded then unblock them. It removes any guesswork, we know what extensions actually need this, and we know that nothing slips through because we didn't account for it.
Flags: needinfo?(mixedpuppy)
(In reply to Shane Caraveo (:mixedpuppy) from comment #27) > it's a problem that may cause privacy and security issues for users. > > If it's possible to pause all outbound network requests, having a permission > in the manifest to mark an extension as blocking startup (or blocking > network I guess) could work. This :)
(In reply to Shane Caraveo (:mixedpuppy) from comment #27) > I don't really see that as a reliable approach, there are way too many > variables to make sure this works right. Having proxy not work isn't a > bummer, it's a problem that may cause privacy and security issues for users. I didn't mean that we would never fix it, I just think it should be addressed separately. > If it's possible to pause all outbound network requests, having a permission > in the manifest to mark an extension as blocking startup (or blocking > network I guess) could work. We just block everything until that set of > extensions are loaded then unblock them. It removes any guesswork, we know > what extensions actually need this, and we know that nothing slips through > because we didn't account for it. So the common ground here is that we're both talking about just suspending some (or all) outgoing network requests at startup, the disagreement is just about whether that's caused by an explicit permission or inferred from the extension's use of apis like webrequest. As I see it the downsides of the explicit permission are that it places an additional demand on extension authors (to understand this new permission and use it when appropriate, and there will certainly be cases where extensions that would want it don't know about it as well as cases where extensions that don't use it unnecessarily). The downside of making it automatic is additional complexity. I don't believe there's any functional shortcoming.
Well, preferably we'd be able to promise that the extensions are loaded during startup, but there's so much going on with that, I'm not certain it's easy to do. It's possible blocking outgoing connections is easier, but see my thoughts further down, it's not completely clear that it is. I'm not convinced that the existence of webRequestBlocking is a good indicator that an extension needs to block for startup, and there is likely a lot of extensions that will have that permission. The proxy permission however probably is a good indicator. Overall I think we require authors to know plenty so I'm not concerned about the additional permission, which can also be documented in the webrequest and proxy sections. I'm also wary of trying to be too smart about it works, we're much more likely to miss something, whether it is a un/necessary filter, or a use case we don't account for. An explicit flag to block everything is clear, will have less code paths to consider and modify, and will cover any missed or future use cases. A tricky part is figuring out when to suspend channels. HTTP Channel for example, doesn't handle on-opening asynchronously right now and on-modify is too late. That could be fixed, other channels may also need some work to support this. I've already been thinking about fixing on-opening since an earlier notification could be handy for some proxy handlers. At that point, relying on on-opening could become murky at best, at worst unusable for proxies on startup.
Depends on: 1407384
Doesn't sound like we need to track this right now.
Kris, Shane, Luca, and I chatted about this the other day. We agreed that the ideal solution here is essentially what was discussed in recent comments: implement a general facility to remember registered event handlers after a background page is deactivated. This stored information should be available early in startup and webextension APIs that implement events should be able to access it during startup before the background page is loaded. These APIs will then also get the capability to tell the webextension framework that they're waiting for a specific background page and get a callback (Promise resolution) when that page has been loaded. Once this capability is in place, we can adapt individual APIs to use it, starting with webRequest and proxy. Note that using this capability from the proxy API will require that the platform's proxy filtering interface become asynchronous which is tracked in bug 1409878. An advantage of this approach is that this capability can also serve as the foundation for implementing Chrome-style event pages. To be clear, it won't get us all the way there and we don't anticipate that extensions for which this bug is important will necessarily benefit from event pages, but it means we have a unified approach for these two different issues (extension startup and event pages) that both boil down to providing a way to have effective event listeners even when an extension background page is not loaded and active. I'll aim to work on this during the 59 cycle, though with the holidays and other concurrent work I'm not sure that we'll actually get a solution landed for 59. I'll probably break this down into a few narrower bugs when I get some focused time to work on it.
Flags: needinfo?(kmaglione+bmo)
I'm very glad to find this bug. > … startup performance. … For myself I learnt – a few months ago – to never simply restart Firefox; to never trust things such as 'Restore Previous Session'. From <https://github.com/gomita/tabscope/issues/67#issuecomment-334995068> (2017-10-08): > … it seems to me (from experiences over a period of months) that > the windows most likely to behave properly, with extensions such as > Tab Scope and Tab Mover, are the windows that have been opened by > Session Manager. More specifically, my learnt workflow is: 1. start Firefox 2. refrain from use 3. await reasonable visual evidence that all extensions have loaded 4. use the 'Load Session…' feature of Session Manager 5. select the required session, typically the most recent 6. click the 'Append to Session' radio button – take care to NOT replace the session 7. Load 8. refrain from using the first window. Tab Mover: <https://addons.mozilla.org/addon/tab-mover/> Side note ========= <https://mozilla.github.io/extension-finder/> directs users of Session Manager to Tab Session Manager but re: <https://github.com/sienori/Tab-Session-Manager/issues/15#issuecomment-338489029> the more modern extension repeatedly, consistently, fails to restore anything with Firefox 56.0.2.
Sorry, a correction to the URL in my side note (above): <https://github.com/sienori/Tab-Session-Manager/issues/15#issuecomment-341031015>
Alias: webextensions-startup
Depends on: 1415975
Blocks: 1415975
No longer depends on: 1415975
Assignee: nobody → aswan
Depends on: 1152332
Kris and Shane, can you look over the general approach in the second patch? Its very much a work in progress, there are some unfinished bits (marked with comments) and it needs tests of course. I think it can also be cleaned up a bit by using things like DefaultMap, but I'd like to get any high-level concerns about the basic approach out of the way before polishing all that stuff up.
Flags: needinfo?(mixedpuppy)
Flags: needinfo?(kmaglione+bmo)
Comment on attachment 8960405 [details] bug 1378459 part 1 event manager refactor https://reviewboard.mozilla.org/r/229186/#review234966 Code analysis found 1 defect in this patch: - 1 defect found by mozlint You can run this analysis locally with: - `./mach lint path/to/file` (JS/Python) If you see a problem in this automated review, please report it here: http://bit.ly/2y9N9Vx ::: browser/components/extensions/ext-devtools-network.js:32 (Diff revision 1) > - targetPromise.then(target => { > + targetPromise.then(target => { > - target.off("navigate", listener); > + target.off("navigate", listener); > - }); > + }); > - }; > + }; > + }, > }).api(), Error: Parsing error: unexpected token } [eslint: None]
Comment on attachment 8960406 [details] bug 1378459 parts 2 through N: implement persistent event listeners https://reviewboard.mozilla.org/r/229188/#review234968 Code analysis found 2 defects in this patch: - 2 defects found by mozlint You can run this analysis locally with: - `./mach lint path/to/file` (JS/Python) If you see a problem in this automated review, please report it here: http://bit.ly/2y9N9Vx ::: toolkit/components/extensions/ext-backgroundPage.js:67 (Diff revision 1) > + if (extension.startupReason != "APP_STARTUP") { > + this.bgPage.build(); > + return; > + } > + > + EventManager.primeListeners(extension); Error: 'eventmanager' is not defined. [eslint: no-undef] ::: toolkit/components/extensions/ext-backgroundPage.js:71 (Diff revision 1) > + > + EventManager.primeListeners(extension); > + > + extension.once("start-background-page", async () => { > + await this.bgPage.build(); > + EventManager.clearPrimedListeners(extension); Error: 'eventmanager' is not defined. [eslint: no-undef]
I think the way this is going looks good. It looks like any call to addListener will persist the listener (if it is persist-able). How about only persisting listeners that are added during background page startup, ignore any added later? For webrequest only those events that can block should be persist-able, any other event wont work this way (and doesn't matter). Looks like it will be easy to do this for proxy.
Flags: needinfo?(mixedpuppy)
Blocks: 1447361
(I already gave my feedback on IRC)
Flags: needinfo?(kmaglione+bmo)
No longer depends on: webext-port-noscript
(In reply to Shane Caraveo (:mixedpuppy) from comment #42) > It looks like any call to addListener will persist the listener (if it is > persist-able). How about only persisting listeners that are added during > background page startup, ignore any added later? That's doable but what's the rationale? We're operating without perfect information here (about whether the listener will be re-registered in a future session) so we have to do some combination of making our best guess and doing what's simplest. And the tradeoff is a hiccup in loading content at startup if we are too aggressive versus the extension not appearing to work correctly if we are too cautious. I would think a typical case for setting up webRequest listeners after startup is when parameters for the listener (eg a filter) are based on some user configuration. In that case, if the user changes configuration at runtime causing a new listener to be added, we would expect the same listener to be added at the next startup. Are you thinking of a particular scenario where an extension is likely to create listeners that don't get created the same way in the next session? > For webrequest only those events that can block should be persist-able, any > other event wont work this way (and doesn't matter). Why shouldn't non-blocking listeners be persistable? We're not changing any of the webRequest mechanics so a non-blocking listener won't suspend the request, it will just store the event details and kick off the background page creation a little earlier than it would otherwise happen. In this way, the extension will actually get to see all the requests that happened during browser startup which I think is desirable. I guess one area of concern is memory consumed by all those queued events in the case that extension startup takes a long time but I think that's a separate issue. Unrelated to Shane's comments, this bug has become a catch-all for issues related to webextensions during browser startup but the patches here are just for handling particular event listeners, so I'm going to split this bug. I'll make this one a tracker and move the event listener work to a more specific one under the tracker.
(In reply to Andrew Swan [:aswan] from comment #44) > (In reply to Shane Caraveo (:mixedpuppy) from comment #42) > > It looks like any call to addListener will persist the listener (if it is > > persist-able). How about only persisting listeners that are added during > > background page startup, ignore any added later? > > That's doable but what's the rationale? It seems like you're clearing any persisted listeners right after initial load/execution of the background page (I frankly cant think of a better time). Thus, any listeners not registered at the top level will not get handled anyway. In real terms those are the only handlers that can block startup. Any listeners done after the initial execution just cannot be persisted. In terms of a traffic monitoring extension (e.g. adblock), I'd have to register a blocking webRequest.onBeforeRequest handler for all urls at the top level of the background page in order to properly block until I'm ready to handle requests. > > For webrequest only those events that can block should be persist-able, any > > other event wont work this way (and doesn't matter). > > Why shouldn't non-blocking listeners be persistable? We're not changing any > of the webRequest mechanics so a non-blocking listener won't suspend the > request, That's a confusing set of negatives. non-blocking listeners never suspend channels. It seems like there is high potential for peristed non-blocking listeners to be called out-of-order (unless the extension purposely uses on onBeforeRequest blocking to force the channel suspend at the beginning of the request). > In this way, > the extension will actually get to see all the requests that happened during > browser startup which I think is desirable. If that is important to an extension, they can use a blocking onBeforeRequest listener. Storing up the data to "replay" events could lead to unexpected behavior in extensions while complicating the implementation.
Another approach to this could be to collect all the match params and persist those. Then use an internal onBeforeRequest handler with those params to block until the background page is ready. Simpler would be to just use all_urls in that, since as soon as one extension uses all_urls (likely) it's going to happen anyway.
Depends on: 1447551
Sorry for the redirection, taking the work on the current webRequest patches over to bug 1447551 so this bug can remain a meta bug for webextensions/startup issues.
Assignee: aswan → nobody
No longer depends on: 1407384
Keywords: meta
Attachment #8960405 - Attachment is obsolete: true
Attachment #8960406 - Attachment is obsolete: true
Depends on: 1457213
Blocks: 1457224
No longer blocks: 1447361
No longer blocks: 1418030
Depends on: 1418030
Blocks: 1437098
Product: Toolkit → WebExtensions
Depends on: 1473757
Component: Untriaged → General
Depends on: 1495200
Summary: Allow some addon functionality to load prior to any content loading → [meta] Allow some addon functionality to load prior to any content loading
Depends on: 1490260
No longer blocks: 1437098
No longer blocks: Session_managers
Priority: P2 → P3

Is this bug 1378459 demonstrated here?

tl;dr AFAICT version 1.0.44 of Malwarebytes Browser Extension (beta) is bugged as described at Scam protection versus CROWDLAAERS … and the bugged aspect is not apparent when an affected page is the first tab loaded at session restoration time.

To workaround the bug in Firefox (and expose the bug with the extension), in this case, either:

  1. reload the page; or
  2. view the page in a second window.

HTH

@Graham
I'm not familiar with the add-on, and don't see any obvious relation between this bug and your video. Could you try to explain in more detail why you think that the observed issue is relevant to this bug?

In essence: it seems that the content (the automatically restored frontmost tab) loads before the extension can take effect.

Attached image Ghostery 8.4.2 with Firefox 69.0 (deleted) —

Firefox 69.0, standard content blocking, extended with Ghostery 8.4.2 alone.

Is this bug 1378459 the likeliest explanation for the extension apparently blocking far less than normal at browser startup time?

https://www.politico.com/, for example – in this case (in this frame from a screen recording):

  • zero trackers blocked with the page loaded at browser startup time
  • twelve blocked with the page subsequently loaded in a new tab.
Attached video Ghostery 8.4.2 with Firefox 69.0 (deleted) —

A compressed version of the screen recording from which the frame in comment 52 was taken.

Hello I am the maintainer of an add-on that makes use of a blocking webRequest.onBeforeRequest listener to intercept and redirect some requests to other domains.

Recently I've had some users report issues with redirects not occurring when Firefox is opened using a link to one of the domains targeted by the add-on, both via the terminal and normally. It would seem that this is an instance of the behaviour described in this bug report. I'm just curious to know if there are plans to address this at some point, as the privacy conscious users of my add-on are understandably not pleased about this behaviour.

Depends on: 1653237

Hi Simon, thanks for your comment. I thought that the startup case was already handled by bug 1447551, but while looking at it again I realized that it is still possible for requests to not be filtered as expected if the request is triggered early enough. I filed bug 1653237 to address that.

Thanks Rob!

Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: