-
Notifications
You must be signed in to change notification settings - Fork 11
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
Waiting for ContentBlockingRules to be applied before navigation #402
Conversation
5f75f05
to
266ef22
Compare
|
||
if navigationAction.isTargetingMainFrame && !url.isDuckDuckGo { | ||
RulesCompilationMonitor.shared.tabWillWaitForRulesCompilation(self) | ||
let waitTime = await webView.configuration.userContentController.awaitContentBlockingRulesInstalled() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it is me but in this particular case await
is making this section pretty hard to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomasstrba you mean the whole method or this particular place?
Do you think getting read of isTargetingMainFrame, isDuckDuckGo check and moving waitTime to inout args will make it better?
like this:
RulesCompilationMonitor.shared.tabWillWaitForRulesCompilation(self)
var waitTime: TimeInterval?
await webView.configuration.userContentController.awaitContentBlockingRulesInstalled(waitTime: &waitTime)
RulesCompilationMonitor.shared.tab(self, didFinishWaitingForRulesWithWaitTime: waitTime)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alex, sorry for jumping in. I will leave the main review to Brindy or Bartek, I just looked to also understand the change. Here are few comments I did while looking at this
} | ||
|
||
if navigationAction.isTargetingMainFrame && !url.isDuckDuckGo { | ||
RulesCompilationMonitor.shared.tabWillWaitForRulesCompilation(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case rules are already compiled and ready, suspending until awaitContentBlockingAssetsInstalled
is finished makes the loading unnecessary slower.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, can we inject RulesCompilationMonitor.shared? It makes it easier to create unit tests of Tab class + it is less spaghetti.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomasstrba, are you sure it's waiting when rules are already compiled? I'll check it but the intent here is it should complete instantly when the rules are in place
Good not about RulesCompilationMonitor.shared
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I understand the intent, but to me it looks like there is a cost of the async/await even if it completes immediately. Since this is the critical section of the loading, I would refactor to speed this as much as possible
@@ -27,4 +27,13 @@ extension Publisher where Failure == Never { | |||
} | |||
} | |||
|
|||
/// Buffers latest published value and replays it on subscription |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the @published property doing the same? This seems a little over-engineered
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically yes, it's mostly the same except for optionality: @published requires property to be present from the start, or making it Optional, ShareReplay is a concept from rxSwift missing in Combine for some reason, it basically waits for a published value and then buffers it for a next subscriptor
Why do you think it lacks clarity? For me it's more complex to have a @published value populated from elsewhere to be connected from elsewhere, shareReplay in contrary allows to have this processing be grouped in one place
} | ||
} | ||
|
||
private let gpcEnabledUpdatesSubject = PassthroughSubject<Bool, Never>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, is there any specific reason why @Published
isn't suitable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't found a good way to combine it with UserDefaultsWrapper, but would be better of course..
@@ -58,7 +58,9 @@ final class Pixel { | |||
guard !dryRun else { | |||
os_log(.debug, log: .pixel, "%@", pixelName.replacingOccurrences(of: "_", with: ".")) | |||
|
|||
onComplete(nil) | |||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, what is the reason for this delay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To simulate a server request, it's a "dryrun" path
|
||
import Foundation | ||
|
||
final class RulesCompilationMonitor: NSObject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Names of methods of this class are describing events triggering them. Looking at this class without looking elsewhere makes it pretty hard to understand. Do you think methods should describe their purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making unit tests to cover this class should help to:
- Verify the functionality
- Improve the naming
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reportWaitTimeForTab(_, waitedForRulesFor: timeInterval) - should be good instead of tab(didFinish).. other methods seem find
I'll add some tests and comments for the class; Considering also renaming it to ContentBlockingRulesCompilationTimeReporter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks 👍 :)
@@ -48,16 +48,21 @@ final class TabCollection: NSObject { | |||
} | |||
|
|||
saveLastRemovedTab(at: index) | |||
tabs[index].tabWillClose() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just thinking. Is there a better way to do this so we don't forget to call tabWillClose()
after adding more remove methods? What is on my mind is subclassing collection (array) and overriding remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TabCollection is basically the collection subclass :)
Maybe adding an "assert" to Tab.dealloc if "tabWillClose" (we may set tab content to .none to indicate this) was called would be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah why not, anything that would help us not to miss tabWillClose() in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job so far - I've been testing rule compilation/recompilation and cache gives awesome boost of performance for already precompiled lists.
There are few bugs I've noticed (find in page, no trackers reported) but I supposed that these are because of the UserScripts reuse issue (mentioned in the comment) - once that is resolved I'll give this another test.
// report only once | ||
isFinished = true | ||
|
||
Pixel.fire(.compileRulesWait(onboardingShown: self.onboardingShown, waitTime: waitTime, result: result), onComplete: completionHandler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have exact time as a parameter?
contentBlockingRulesSubject.eraseToAnyPublisher() | ||
init(rulesUpdate: ContentBlockerRulesManager.UpdateEvent) { | ||
self.rulesUpdate = rulesUpdate | ||
self.userScripts = UserScripts(with: DefaultScriptSourceProvider()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like a correct approach: here we create set of user scripts and sent these to all tabs - so whatever tab is created last will become the sole delegate of the scripts. Instead scripts should be created for every Tab separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, how could I miss it 🤦♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries - I've read that code like 5 times and didn't notice the problem until I started the testing 🙃
should be good to go now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, scripts seems to work well now, I've left one comment related to performance inline.
One thing that bothers me is that I randomly get assertion from UserContentController.installUserScripts
:
guard let delegate = delegate else {
assertionFailure("UserContentController delegate not set")
return
}
I tried to figure out a way to reproduce that but it seems random - I just leave browser working in background for a while, and sometimes when I come back and trigger protection on/off it causes assertion to fire.
@bwaresiak I can swear I've caught this assertion once thinking I understand where it comes from, but no luck to reproduce it since then.. |
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self, | ||
weak cc=webView.configuration.userContentController] in | ||
assert(self == nil) | ||
assert(cc == nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't that fire randomly in some cases e.g when we call faviconManagement.handleFaviconLinks
and have to wait for response, but tab is closed? We probably have more async blocks that retain the Tab instance temporarily that could cause this to misfire - but prolly we should change all these blocks to use weak self anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll just remove this to let it pass now and make another TechDebt task for this to think about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job @mallexxx!
…ntication # By Alexey Martemyanov (1) and others * sam/logins-filtering-and-sorting: Disable the search field when editing. Waiting for ContentBlockingRules to be applied before navigation (#402) Cookie prompt management (#312) Pass config data to Autofill UserScript (#418) # Conflicts: # DuckDuckGo.xcodeproj/project.pbxproj # DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift # DuckDuckGo/Preferences/Model/PreferenceSections.swift # DuckDuckGo/Preferences/View/PrivacySecurityPreferencesTableCellView.xib
* rm WebViewConfiguration typealias * async decidePolicyForNavigationAction + wait for content rules * Rules compilation monitor and reporting * Inject UserScripts+ContentBlockingRules as a single ContentBlockingAssets * PR review fixes * fix tests * fix proj * ContentBlockingUpdatingTests and fixes * cleanup * cleanup * fixing tests * fixing tests * Report no wait time on rules loading; Report contentBlockingCompilationTime * fix tests * still fixing tests * and more * linter, please * please.. * the tests * c‘mon! * get rid of it * what you say about no runloop waiting? * figuring out what's wrong * at last * bump BSK * UserScripts generated per-tab * fix unexpected Pixel during tests * compileRulesWait Pixel +waitTime argument * fixing PR issues * leaks prevention * fix tests * cleanup * set BSK to v.10.0.0 * final adjustment
Task/Issue URL: https://app.asana.com/0/72649045549333/1201190116754047
Tech Design URL: https://app.asana.com/0/inbox/1199237043628108/1201676989048031/1201672261001531
CC: @tomasstrba
Description:
see duckduckgo/BrowserServicesKit#54
Steps to test this PR:
0. Remove ~/Library/WebKit/com.duckduckgo.macos.browser[.debug]/ContentRuleLists/*
Testing checklist:
Internal references:
Software Engineering Expectations
Technical Design Template
When ready for review, remember to post the PR in MM