Closed Bug 1210583 Opened 9 years ago Closed 9 years ago

Callback argument ignored by tabs.executeScript and tabs.insertCss

Categories

(WebExtensions :: Untriaged, defect, P1)

defect

Tracking

(firefox47 fixed)

RESOLVED FIXED
mozilla47
Iteration:
46.3 - Jan 25
Tracking Status
firefox47 --- fixed

People

(Reporter: kmag, Assigned: kmag, Mentored)

References

Details

(Keywords: dev-doc-complete, Whiteboard: [tabs])

Attachments

(2 files)

+++ This bug was initially created as a clone of Bug #1208257 +++ The functions accept a callback argument, but it's never actually called.
Whiteboard: [tabs]
Assignee: kmaglione+bmo → nobody
Mentor: kmaglione+bmo
Whiteboard: [tabs] → [tabs][good first bug]
Flags: blocking-webextensions+
Blocks: 1223254
I'd love to work on this bug if I could get some more information in terms of what needs to happen here. Thanks!
Flags: needinfo?(kmaglione+bmo)
Essentially, when an extension calls `executeScript` or `insertCSS`, we need to send a message to the content process to execute that script. After the message has been received by the content process, we need to execute any callback passed by the extension code. The executeScript APIs are here: https://dxr.mozilla.org/mozilla-central/source/browser/components/extensions/ext-tabs.js#466 And the message is processed by the content script here: https://dxr.mozilla.org/mozilla-central/source/toolkit/components/extensions/ExtensionContent.jsm#554 The tricky part is that we need to store a reference to the callback when `executeScript` is called, and execute it only when we've received the right message from the content process. I can provide some suggestions on how to do that, if you'd like.
Flags: needinfo?(kmaglione+bmo)
Assignee: nobody → kmaglione+bmo
Whiteboard: [tabs][good first bug] → [tabs]
Iteration: --- → 45.3 - Dec 14
Iteration: 45.3 - Dec 14 → 46.2 - Jan 11
Iteration: 46.2 - Jan 11 → 46.1 - Dec 28
Iteration: 46.1 - Dec 28 → 46.2 - Jan 11
Blocks: 1231819
Iteration: 46.2 - Jan 11 → 46.3 - Jan 25
Blocks: 1190685
I'm planning to eventually extend this to support to-way messaging across an open channel, hence the module name. I'm also trying to keep it as generic as possible, so I can hopefully make it available for use in the rest of the tree once it matures. This overlaps a lot with the Broker/Messenger/Port functionality. It might be nice to merge some more of that functionality into this interface at some point, but I'm not sure. In the mean time, we should probably just update it to use this class to handle the one-way reply handling logic that it implements in a few places. Review commit: https://reviewboard.mozilla.org/r/31529/diff/#index_header See other reviews: https://reviewboard.mozilla.org/r/31529/
Attachment #8709736 - Flags: review?(wmccloskey)
Hi Kris, I'm having some trouble understanding what's going on here. One thing I'm worried about is that it seems like all the messages end up with MessageChannel:Message as the message name. Is there a reason you can't use something more specific? Also, can you explain what the different advantages of this class are over a normal message manager? It seems like it supports filtering and replies. Is there anything I'm missing? It looks like MessageBroker is a wrapper around a message manager that does filtering. Could you name it FilteringMessageManager or something like that? (Sorry, I realize the broker name is my fault.) I also wonder if it would be easier to make message managers wrapper cached than to have to implement this BrokerManager thing. Then we could just have a global weakmap of FilteringMessageManagers in MessageChannel.jsm, which seems easier.
Olli, how hard would it be to make message managers wrapper cached so that we can put them in weakmaps? It might make some stuff in this bug somewhat easier.
Flags: needinfo?(bugs)
(In reply to Bill McCloskey (:billm) from comment #5) > Hi Kris, I'm having some trouble understanding what's going on here. One > thing I'm worried about is that it seems like all the messages end up with > MessageChannel:Message as the message name. Is there a reason you can't use > something more specific? I considered using a separate message listener for every message name for a while. I decided against it because it would have added a lot of complexity to have to manage a separate listener for each message rather than one listener per manager, and didn't seem to have a lot of benefit. > Also, can you explain what the different advantages of this class are over a > normal message manager? It seems like it supports filtering and replies. Is > there anything I'm missing? The main purpose is to route replies back to the sender, and to do it in a way that makes sure replies are either answered, or rejected, without making it too easy to leak. The filtering is there mostly so the broker can make sure that each message has exactly one recipient and one reply, without making it too difficult for multiple extensions to listen for the same message on the same message manager. I could make a good argument for taking it out, though. > It looks like MessageBroker is a wrapper around a message manager that does > filtering. Could you name it FilteringMessageManager or something like that? > (Sorry, I realize the broker name is my fault.) I wasn't too happy with the name either, in the end. I guess FilteringMessageManager works. > I also wonder if it would be easier to make message managers wrapper cached > than to have to implement this BrokerManager thing. Then we could just have > a global weakmap of FilteringMessageManagers in MessageChannel.jsm, which > seems easier. I was thinking about that too. I'm not sure how much it would simplify things, in this case, since the main purpose of the message-manager-disconnect observer is to cancel pending replies. A WeakMap would remove the need for the unload listener and some chances of leaks, though.
crap, midair collision.... ok what did I write. I thought first wrappercache case would be bug 888600, but actually it should be just a small subset, and it applies to parent side only since on child side mm are being another object which is already wrappercache (because of inheriting DOMEventTargetHelper). So, extend nsWrapperCache, add the relevant stuff to traverse and unlink and modify nsDOMClassInfo.* so that wrappercaching is actually enabled when expando properties and such are added. See EventTargetSH there.
Flags: needinfo?(bugs)
(In reply to Kris Maglione [:kmag] from comment #7) > (In reply to Bill McCloskey (:billm) from comment #5) > > Hi Kris, I'm having some trouble understanding what's going on here. One > > thing I'm worried about is that it seems like all the messages end up with > > MessageChannel:Message as the message name. Is there a reason you can't use > > something more specific? > > I considered using a separate message listener for every message name for a > while. I decided against it because it would have added a lot of complexity > to have to manage a separate listener for each message rather than one > listener per manager, and didn't seem to have a lot of benefit. I suppose why it would make it more complicated isn't immediately obvious, though: * If I send a message on a message manager, I want to know if it failed to reach the other side. With a single message name for all listeners, as long as there's any listener on the other side of the message manager, I'll get a reply. If I'm expecting a content script context to have added a listener for a window, and there's a race, I'll at least know not to expect a reply. * When there are multiple listeners on the same message, there needs to be exactly one reply. Or, at least, adding multiple listeners shouldn't result in multiple listeners being called and some of their replies being lost. With multiple message names, that still means two levels of maps, with the added complexity of adding and removing more listeners, and the added risk of messages failing to find a recipient. * The same problems apply to managing replies. With multiple message names, unexpected extra replies are silently ignored. We also need to separately keep track of pending replies so they can be canceled when a message manager dies, or a context is closed. Sharing the multiplexing code between the requests and replies makes this easier, and multiplexing on one message name makes things easier for both.
IIRC, putting things in weakmaps requires full WebIDLification.
Sorry this is taking so long Kris. I didn't realize how big a patch it was when I said I could review it by yesterday. I should have more time next week once the merge happens. Andrew, are you sure about the WebIDL restriction? The code seems to try to work for wrapped natives: https://dxr.mozilla.org/mozilla-central/source/js/src/builtin/WeakMapObject.cpp#114
(In reply to Bill McCloskey (:billm) from comment #11) > Sorry this is taking so long Kris. I didn't realize how big a patch it was > when I said I could review it by yesterday. I should have more time next > week once the merge happens. That's fine. Given how long it took me to write it, I really shouldn't have expected you to review it that quickly. > Andrew, are you sure about the WebIDL restriction? The code seems to try to > work for wrapped natives: > https://dxr.mozilla.org/mozilla-central/source/js/src/builtin/WeakMapObject.cpp#114 I wound up looking into this yesterday. I initially thought it should work on other types of wrapped native too, but the preserveWrapperCallback we use only handles WebIDL objects. I think we may have other options, though. We could always store the message manager owner rather than the message manager itself. For message managers without owners, we could create a stub owner object and store it in a symbol property on the message manager. I'm pretty sure that as long as the message manager has any listeners that are holding a reference to the JS reflector alive, that should keep both the expando and the weak map key alive. Someone can correct me if I'm wrong. That's not as nice as being able to use the message manager as a key directly, but it might be nicer than having to use a Map.
As Kris said, the question is what happens here in the Gecko callback: https://dxr.mozilla.org/mozilla-central/source/js/xpconnect/src/XPCJSRuntime.cpp#3197 We used to have a hacked up bit of code in there for nsINode, before they were converted to WebIDL. Something similar could probably be special-cased in for message managers if needed.
Comment on attachment 8709736 [details] MozReview Request: Bug 1210583: Part 1 - [webext] Add support for cross-process messaging with async responses. r?billm https://reviewboard.mozilla.org/r/31529/#review28417 In my gut I feel like this is maybe a little overkill. I think we would end up with the best design if we kept the sendMessage/Port code separate from this. It needs extra complexity that this code doesn't. For example, it looks like it would still be a good amount of work to handle multiple browser.runtime.onMessage listeners but ensure that only one of them actually sends a reply. That said, it's hard to design something like this when we don't know quite how it's going to be used. I'm fine landing this and seeing how it goes. Once Luca's bug and bug 1197346 land, we'll hopefully have a better picture of what we need here. ::: browser/components/extensions/ext-tabs.js:487 (Diff revision 1) > + MessageChannel.sendMessage(mm, "Extension:Execute", { options }, recipient); I think b2g might break if you don't import MessageChannel in this file. Even if it doesn't, it would be nice to do. ::: toolkit/components/extensions/ExtensionUtils.jsm:789 (Diff revision 1) > + MessageChannel, This is a little unconventional. I guess it's more uniform this way, but I think I'd still rather do direct Cu.import from each module that uses MessageChannel. ::: toolkit/components/extensions/MessageChannel.jsm:7 (Diff revision 1) > +this.EXPORTED_SYMBOLS = ["MessageChannel"]; Please add a big comment at the top of the file with an example of how to use MessageChannel from the perspective of the client. The inline comments are less useful because it's not clear which ones are meant for someone changing this file and which ones are useful for someone using MessageChannel. ::: toolkit/components/extensions/MessageChannel.jsm:32 (Diff revision 1) > +class MessageBroker { Please do rename this to FilteringMessageManager. ::: toolkit/components/extensions/MessageChannel.jsm:33 (Diff revision 1) > + /** The indentation of this comment seems wrong. ::: toolkit/components/extensions/MessageChannel.jsm:92 (Diff revision 1) > + let handlers = this.handlers.get(messageName) || []; Would be clearer to say "|| new Set()". ::: toolkit/components/extensions/MessageChannel.jsm:152 (Diff revision 1) > +class BrokerManager extends Map { FilteringMessageManagerMap? ::: toolkit/components/extensions/MessageChannel.jsm:438 (Diff revision 1) > + abortResponses(sender, reason = this.REASON_DISCONNECTED) { For abstraction reasons, I think it would be nicer if this delegated to FilteringMessageManager. ::: toolkit/components/extensions/MessageChannel.jsm:439 (Diff revision 1) > + for (let broker of this.responseBrokers.values()) { I guess this makes it hard to use a WeakMap. I don't really see a way around that. ::: toolkit/components/extensions/MessageChannel.jsm:459 (Diff revision 1) > + abortBroker(target, reason) { Same here about delegating to FMM.
Attachment #8709736 - Flags: review?(wmccloskey)
Comment on attachment 8709737 [details] MozReview Request: Bug 1210583: Part 2 - [webext] Support callbacks in tabs.executeScript/tabs.insertCSS. r=billm https://reviewboard.mozilla.org/r/31531/#review28421 ::: browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js:25 (Diff revision 1) > + browser.test.assertEq(undefined, result, "Expected callback result"); My understanding is that Chrome returns 27 here. Maybe I'm wrong? ::: toolkit/components/extensions/Extension.jsm:284 (Diff revision 1) > + sender.pageId = this.contextId; Why pageId and not contextId? ::: toolkit/components/extensions/Extension.jsm:315 (Diff revision 1) > + MessageChannel.abortResponses({ extensionId: this.extension.id, Generally I think it's nicer to do: functionCall({ prop1: val1, prop2: val2, }); since you get less indentation. ::: toolkit/components/extensions/ExtensionContent.jsm:156 (Diff revision 1) > + Why the extra line? ::: toolkit/components/extensions/ExtensionContent.jsm:190 (Diff revision 1) > runSafeSyncWithoutClone(Services.scriptloader.loadSubScriptWithOptions, url, options); Shouldn't we catch errors here too? Also, I think Chrome treats the result of the last statement (if it's an expression) as the result in this case. We would need platform support for that. Could you please file a bug? ::: toolkit/components/extensions/ExtensionContent.jsm:198 (Diff revision 1) > + this.deferred.reject(e.message); Return here before also resolving the promise?
Attachment #8709737 - Flags: review?(wmccloskey) → review+
(In reply to Bill McCloskey (:billm) from comment #15) > In my gut I feel like this is maybe a little overkill. I'd agree you if it were only for one or two uses, but I keep running into places where we need this kind of functionality: * tabs.executeScript, insertCSS, captureVisibleTab, sendMessage, ... * webNavigation.getFrame, getAllFrames * runtime.sendMessage, onMessage, ... Not to mention, I expect, just about every method call for OOP add-ons. At the moment it already adds up to at least a half dozen cases where we're manually using context unload listeners and ad-hoc response ID tracking to handle replies, and still missing corner cases. > I think we would end up with the best design if we kept the sendMessage/Port > code separate from this. It needs extra complexity that this code doesn't. > For example, it looks like it would still be a good amount of work to handle > multiple browser.runtime.onMessage listeners but ensure that only one of > them actually sends a reply. I don't really have a strong opinion about that either way. I do still think we should probably use it for some parts of that code, I'm just not sure how much. I did consider whether it would be useful for onMessage with multiple listeners. I have a few options, but I was leaning towards adding a flag to allow multiple listeners, and choose the response using Promise.race, which I think would pretty simple. > This is a little unconventional. I guess it's more uniform this way, but I > think I'd still rather do direct Cu.import from each module that uses > MessageChannel. Yeah, I think that probably makes more sense. > ::: toolkit/components/extensions/MessageChannel.jsm:92 > (Diff revision 1) > > + let handlers = this.handlers.get(messageName) || []; > > Would be clearer to say "|| new Set()". Yeah. The property started out as an array rather than a set. > For abstraction reasons, I think it would be nicer if this delegated to > FilteringMessageManager. That gets complicated. I tried to do it at first, but the message manger doesn't know enough about the handlers to cancel them. And canceling only makes sense for response listeners, not message listeners. I'll think about storing a separate set of pending responses outside the brokers. > ::: toolkit/components/extensions/MessageChannel.jsm:439 > (Diff revision 1) > > + for (let broker of this.responseBrokers.values()) { > > I guess this makes it hard to use a WeakMap. I don't really see a way around > that. We only need to know about pending responses. If I store those in a separate set, we can still use WeakMaps for the message managers.
(In reply to Bill McCloskey (:billm) from comment #16) > > + browser.test.assertEq(undefined, result, "Expected callback result"); > > My understanding is that Chrome returns 27 here. Maybe I'm wrong? It does. I was going to document it as an incompatibility because I didn't want to have to use evalInSandbox for script files. It looks like loadSubScript does return the result of the evaluation, though, so I guess it's easy enough to fix. > ::: toolkit/components/extensions/Extension.jsm:284 > (Diff revision 1) > > + sender.pageId = this.contextId; > > Why pageId and not contextId? Honestly, I spent too much time thinking about what to name it, and eventually gave up. The class calls itself ExtensionPage, we call the variable `context`, and the API calls them views. It should probably be contextId, though, yeah. > ::: toolkit/components/extensions/ExtensionContent.jsm:156 > (Diff revision 1) > > + > > Why the extra line? Merge botch. > ::: toolkit/components/extensions/ExtensionContent.jsm:190 > (Diff revision 1) > > runSafeSyncWithoutClone(Services.scriptloader.loadSubScriptWithOptions, url, options); > > Shouldn't we catch errors here too? Probably. We should also catch them for the CSS, if it comes to it. But that requires removing the runSafeSyncWithoutClone calls, which leaves the question of what to do when there are multiple errors, and when we're loading scripts defined in the manifest. So I decided I'd leave it until I implemented `runAt` and `allFrames`, since they have the same problems. > Also, I think Chrome treats the result of the last statement (if it's an > expression) as the result in this case. We would need platform support for > that. Could you please file a bug? It looks like we already have support, so it's easy enough to fix. > ::: toolkit/components/extensions/ExtensionContent.jsm:198 > (Diff revision 1) > > + this.deferred.reject(e.message); > > Return here before also resolving the promise? It shouldn't make a difference. I decided against it because I'm pretty sure in most cases we should keep going, even if there's an error, and just return the result of the first resolution or rejection to the caller. I'm going to look into it more for runAt and allFrames support.
Attachment #8709736 - Flags: review?(wmccloskey)
Comment on attachment 8709736 [details] MozReview Request: Bug 1210583: Part 1 - [webext] Add support for cross-process messaging with async responses. r?billm Review request updated; see interdiff: https://reviewboard.mozilla.org/r/31529/diff/1-2/
Comment on attachment 8709737 [details] MozReview Request: Bug 1210583: Part 2 - [webext] Support callbacks in tabs.executeScript/tabs.insertCSS. r=billm Review request updated; see interdiff: https://reviewboard.mozilla.org/r/31531/diff/1-2/
Attachment #8709737 - Attachment description: MozReview Request: Bug 1210583: Part 2 - [webext] Support callbacks in tabs.executeScript/tabs.insertCSS. r?billm → MozReview Request: Bug 1210583: Part 2 - [webext] Support callbacks in tabs.executeScript/tabs.insertCSS. r=billm
I can't say that I disagree that this feels too complicated. I'll probably wind up reworking it at some point to simplify things, but this is the kind of thing that I'll work on for months before releasing if I don't stop myself and go with an initial implementation that works. At this point, I'm more interested in the interface than the implementation. If it turns out to be too complicated, or unhelpful, I'm happy to rewrite it, or rip it out and do something else. At the moment, I'm happy with the way it works for my callback use cases. I'm not in a rush to make changes to the messenger/port code, because so far it's working pretty well. But I'm keeping it in mind as a use case, because I think this kind of callback logic is inherently brittle and leak prone, and the fewer different places we have to implement it/think about it, the better.
Comment on attachment 8709736 [details] MozReview Request: Bug 1210583: Part 1 - [webext] Add support for cross-process messaging with async responses. r?billm https://reviewboard.mozilla.org/r/31529/#review29285 ::: toolkit/components/extensions/MessageChannel.jsm:14 (Diff revision 2) > + * Since each message may have only one recipient, the listener end may s/may/must/
Attachment #8709736 - Flags: review?(wmccloskey) → review+
https://hg.mozilla.org/integration/fx-team/rev/65b634919d299de20908a8dbffe33ba8a4ab6ad9 Bug 1210583: Part 1 - [webext] Add support for cross-process messaging with async responses. r=billm https://hg.mozilla.org/integration/fx-team/rev/261e997621c182a03bc330c8bd18c98eddb9c7eb Bug 1210583: Part 2 - [webext] Support callbacks in tabs.executeScript/tabs.insertCSS. r=billm
Status: NEW → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla47
Product: Toolkit → WebExtensions
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: