Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #339 by normalizing localStorage and browser.storage.local usage #395

Closed
wants to merge 1 commit into from

Conversation

andreineculau
Copy link

  1. I haven't tested this
  2. in browsers apps/extensions, due to the async nature, calling debug before we read from browser.storage.local may end up in lost messages

@coveralls
Copy link

coveralls commented Dec 24, 2016

Coverage Status

Coverage remained the same at 63.462% when pulling fe77c4b on andreineculau:patch-1 into 280a64d on visionmedia:master.

@thebigredgeek
Copy link
Contributor

I'd feel better merging this if we had some test coverage around local storage. This seems like an accident waiting to happen without it :/. Is there any way you could add tests around the original code and make sure all is well after plugging in your changes? We need better coverage anyway :p

@eliihen
Copy link

eliihen commented Jan 14, 2017

I just applied this patch, and can report the following:

  • Before: debug in my WebExtension is logging absolutely nothing, no matter where I set debug to *.
  • After: debug logs as normal, but it is reading from localStorage, which doesn't look like what was intended.

Any chance of this getting updated and merged?

if (browser.storage.local.get.length === 2) {
// Chrome/Opera/Edge use the callback pattern
return browser.storage.local;
} else if (browser.storage.local.get.length === 1) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing in a Firefox 52a2 WebExtension, this returns 0, which means Firefox falls back to localStorage.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@esphen true that, I've seen the same (arity is 0 because they implement the functions with dynamic arguments).
The PR has been amended with a commit my team had in our private fork.

@andreineculau
Copy link
Author

@thebigredgeek I agree with you on increasing the coverage, but it won't be me fixing that. Sorry.

if (null == namespaces) {
exports.storage.remove('debug');
} else {
exports.storage.set('debug', namespaces);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firefox: Error: Incorrect argument types for storage.StorageArea.set.

@coveralls
Copy link

coveralls commented Jan 14, 2017

Coverage Status

Coverage remained the same at 63.75% when pulling 6e0d190 on andreineculau:patch-1 into 1c163a4 on visionmedia:master.

@andreineculau
Copy link
Author

@esphen better? thanks for debugging! (I cannot test at the moment)

@coveralls
Copy link

coveralls commented Jan 14, 2017

Coverage Status

Coverage remained the same at 63.75% when pulling 0dac9b6 on andreineculau:patch-1 into 1c163a4 on visionmedia:master.

@eliihen
Copy link

eliihen commented Jan 14, 2017

No problem söta bror, I'm just as interested in seeing this land as you!

Yep, this is progress. No longer seeing Incorrect argument types for storage.StorageArea.set there.

However now I am seeing TypeError: (intermediate value).split is not a function at this line. namespaces is coming into the enable method as the following, which does not support being split as a string:

{ debug: "*" }

@eliihen
Copy link

eliihen commented Jan 14, 2017

I think storage.get isn't quite right according to the return value of the promise. Observe the following:

> browser.storage.local.set({ 'debug': '*' })
< Promise { <state>: "pending" }
> browser.storage.local.get('debug').then(console.log)
< Promise { <state>: "pending" }
< Object { debug: "*" }

Copy link

@eliihen eliihen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We got some more issues to resolve before this will work. Most notable the wonky user-agent check and some async issues.


if (typeof navigator !== 'undefined' &&
typeof navigator.userAgent !== 'undefined') {
isFirefox = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is a bit wonky for me because I am using the random agent spoofer extension. Suddenly it stopped working for no particular reason, as it randomizes the agent every now and then.

Can we find a better way to check than this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try my patch-1-without-ua-sniff branch https://github.com/andreineculau/debug/tree/patch-1-without-ua-sniff although I find it utterly wrong for an extension to do that even for other extensions.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good news: I just tested the following in Firefox:

> browser.storage.local.get('debug', console.log)
< undefined
< Object { debug: "*" }

In other words, you don't actually need any browser detection here, as Firefox supports the callback pattern in addition to the promise pattern.

// Firefox uses the Promise pattern
get: function(key, cb) {
browser.storage.local.get(key).then(cb);
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be

        get: function(key, cb) {
          browser.storage.local.get(key).then(function(result) {
            cb(result && result.debug);
          });
        },

Or similar

if (typeof process !== 'undefined' && 'env' in process) {
return process.env.DEBUG;
if (exports.storage) {
exports.storage.get('debug', exports.enable);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another problem I am seeing is that enabled is running before enable runs here, and thus exports.names are not set when it is checking for what to enable. This breaks the enabled check and ensures logging is disabled for the duration of the page load.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow you here at all. There's no enabled and there's no exports.names. Care to rephrase?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'll elaborate:

There's no enabled and there's no exports.names

Not in this file, no. We need to jump over to debug.js to understand what happens. In that file there is a method called enabled. It is run from createDebug, i.e. when debug('foo') is called. Currently, we're in a situation where enabled is very likely to be called before exports.names is set, which is a precondition of that method. Thus we are breaking the contract of enabled.

exports.names are set by enable, which is passed as a callback in load. Problem is, now that storage.get is async, the order which the assignment happens is mixed up. With localStorage we can assume synchronous behavior and it works, but this is not the case for WebExtensions.

import debug from 'debug';
const log = debug('foo');

log('Hello!'); // <-- Never logs because debug was not enabled when `debug('foo')` was called

setTimeout(() => {
  debug('bar')('Hi!'); // <-- (Probably) runs because the promise has resolved and the callback fired
}, 1000);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, you're pointing out point no. 2 in the description of this PR

in browsers apps/extensions, due to the async nature, calling debug before we read from browser.storage.local may end up in lost messages

Comments:

  1. our workaround is to queue up messages, and process them after the local storage value is read
  2. I see this fixed as part of another issue/PR (the goal is to support chrome.storage.local properly now)
  3. the risk is rather small that messages are lost, given that the main execution is deferred (setTimeout 0)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct, I am indeed pointing that out.

Basically what you are arguing is that this PR is making the situation better, as debug goes from not working at all to working in most cases. I can buy that, but I don't buy that the risk is small. I 100% consistently saw that messages that were logged by a logger assigned directly after the import debug from 'debug'; did not appear.

Let's make it clear that I'm not saying it's a deal breaker. It would be awesome to debug working. We just may need to make clear that the following is the way you need to be logging in a WebExtension until we can improve it further. Example using React.js:

import debug from 'debug';
// const log = debug('FooComponent'); // <-- DO NOT DO THIS

export default function FooComponent() {
  debug('FooComponent')('Rendering FooComponent');
  // log('Rendering FooComponent'); // <-- DO NOT DO THIS

  return <h1>Foo</h1>;
}

@coveralls
Copy link

coveralls commented Jan 15, 2017

Coverage Status

Coverage remained the same at 63.75% when pulling 1051435 on andreineculau:patch-1 into 1c163a4 on visionmedia:master.

@andreineculau
Copy link
Author

@esphen storage.get should be fixed now

@coveralls
Copy link

coveralls commented Jan 15, 2017

Coverage Status

Coverage remained the same at 63.75% when pulling ead58f8 on andreineculau:patch-1 into 1c163a4 on visionmedia:master.

@andreineculau
Copy link
Author

andreineculau commented Jan 15, 2017 via email

Copy link

@eliihen eliihen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified that the current patch works as intended in my WebExtension.

Provided the maintainers (@thebigredgeek?) is okay with the known bug mentioned in the review, I don't see why we shouldn't merge this and get support for WebExtensions.

@thebigredgeek
Copy link
Contributor

@andreineculau are we sure this is still necessary? A lot of fixes have been applied since January :p

@andreineculau
Copy link
Author

andreineculau commented Apr 6, 2017

@thebigredgeek oh, in that case, no

@andreineculau
Copy link
Author

andreineculau commented Apr 6, 2017 via email

@scholtzm
Copy link

scholtzm commented Apr 25, 2017

Seems like the initial PR (#143) that introduced chrome.storage.local was never really tested since the API is async (docs) and completely different to localStorage. It also managed to break WebExtensions.

This lead to another PR (#190) which just added extra complexity to API and is not even documented anywhere.

If we reverted pull request #143, WebExtensions support would be restored and we would also remove "support" for Chrome Apps which never really worked.

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants