Fix Bug 1433230 - Add recent downloads to Highlights #4076
Conversation
13e4d5f
to
04dea6b
| @@ -241,7 +241,7 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel { | |||
| browser.renderLayers = true; | |||
| } | |||
|
|
|||
| this.onActionFromContent({type: at.NEW_TAB_LOAD}, msg.target.portID); | |||
| this.onActionFromContent({type: at.NEW_TAB_LOAD, _target: msg.target}, msg.target.portID); | |||
sarracini
Apr 5, 2018
Author
Contributor
DownloadsCommon.getData needs a browser, so we just tack on the browser here so that I can access it in DownloadsManager.init()
DownloadsCommon.getData needs a browser, so we just tack on the browser here so that I can access it in DownloadsManager.init()
Mardak
Apr 12, 2018
Member
This set of changes relating to _target shouldn't be needed anymore?
This set of changes relating to _target shouldn't be needed anymore?
| this._viewableDownloadItems = new Map(); | ||
| } | ||
|
|
||
| addStore(store) { |
sarracini
Apr 5, 2018
Author
Contributor
I'm not too crazy about this solution here... any better ideas are welcome @Mardak / @k88hudson
I'm not too crazy about this solution here... any better ideas are welcome @Mardak / @k88hudson
Mardak
Apr 9, 2018
Member
Should this just be a Feed, which would automatically get a store? I suppose the sub option for highlights would just directly toggle the feed on/off… ??
Should this just be a Feed, which would automatically get a store? I suppose the sub option for highlights would just directly toggle the feed on/off… ??
| describe("#onAction", () => { | ||
| it("should add a DownloadsCommon view on NEW_TAB_LOAD", () => { | ||
| let stub = global.DownloadsCommon.getData().addView; | ||
| stub.reset(); |
sarracini
Apr 5, 2018
•
Author
Contributor
I think stub.reset() may be removed from their API soon...... ¯_(ツ)_/¯
I think stub.reset() may be removed from their API soon...... ¯_(ツ)_/¯
|
Playing around with the PR felt somewhat odd -- partially because the hovered url in the "status bar" is not what happens on click and also just the behavior of launching applications. We probably want some security feedback on adding this feature… Also, the new tab -> init behavior and your I think we should chat more about the approach and reevaluate before touching too much more. |
| # link that belongs to this downloaded item" | ||
| menu_action_copy_download_link=Copy Download link | ||
| menu_action_go_to_download_page=Go to Download page | ||
| menu_action_remove_download=Remove from history |
Mardak
Apr 9, 2018
Member
These strings don't seem to match the casing of our other menu strings. In particular it looks like words are generally uppercase except prepositions?
These strings don't seem to match the casing of our other menu strings. In particular it looks like words are generally uppercase except prepositions?
sarracini
Apr 10, 2018
Author
Contributor
Yeah I know, I've been back and forth with design and they told me to be consistent with our context menus in activity stream
Yeah I know, I've been back and forth with design and they told me to be consistent with our context menus in activity stream
Mardak
Apr 12, 2018
Member
It would seem that being "consistent with our context menus" would mean these strings should be "Copy Download Link" "Go to Download Page" "Remove from History" etc… ?
It would seem that being "consistent with our context menus" would mean these strings should be "Copy Download Link" "Go to Download Page" "Remove from History" etc… ?
| ShowFile: (site, index, eventSource, isEnabled, siteInfo, platform) => ({ | ||
| id: _GetPlatformString(platform), | ||
| icon: "search", | ||
| action: ac.AlsoToMain({ |
Mardak
Apr 9, 2018
Member
Do these actions want to be Also or just Only? I don't think content does anything with these?
Do these actions want to be Also or just Only? I don't think content does anything with these?
sarracini
Apr 10, 2018
Author
Contributor
Ah good call, these should be Only
Ah good call, these should be Only
| if (this._store.getState().Prefs.values["section.highlights.includeDownloads"]) { | ||
| this._browser = action._target.browser; | ||
| this._downloadData = DownloadsCommon.getData(this._browser.ownerGlobal, true, false, true); | ||
| this._downloadData.addView(this); |
Mardak
Apr 9, 2018
Member
This doesn't seem quite right of attempting to add a per-tab view vs a single activity stream view / feed. It seems like this actually doesn't result in multiple views as addView(this) happens to try to add the same object on a second init? Although then does this have problems on tab close / uninit if called multiple times?
This doesn't seem quite right of attempting to add a per-tab view vs a single activity stream view / feed. It seems like this actually doesn't result in multiple views as addView(this) happens to try to add the same object on a second init? Although then does this have problems on tab close / uninit if called multiple times?
| this._viewableDownloadItems = new Map(); | ||
| } | ||
|
|
||
| addStore(store) { |
Mardak
Apr 9, 2018
Member
Should this just be a Feed, which would automatically get a store? I suppose the sub option for highlights would just directly toggle the feed on/off… ??
Should this just be a Feed, which would automatically get a store? I suppose the sub option for highlights would just directly toggle the feed on/off… ??
| break; | ||
| case at.OPEN_LINK: { | ||
| if (action.data.type === "download") { | ||
| this.openFile(action.data.url); |
Mardak
Apr 9, 2018
Member
I suppose this is by design, but in testing, something feels a bit wrong to launch files / applications by clicking a link in content space… Somewhat related of how to prevent malicious launching of other files? I suppose this happens to require a file be downloaded.. Although this "type" could be faked…
I suppose this is by design, but in testing, something feels a bit wrong to launch files / applications by clicking a link in content space… Somewhat related of how to prevent malicious launching of other files? I suppose this happens to require a file be downloaded.. Although this "type" could be faked…
| const downloadedItem = element.download; | ||
| return { | ||
| hostname: new URL(downloadedItem.source.url).hostname, | ||
| url: downloadedItem.source.url, |
Mardak
Apr 9, 2018
Member
This causes the status / hovered link url (at the bottom corner of the window) to show up as the download source, but clicking the link / card takes you elsewhere.
This causes the status / hovered link url (at the bottom corner of the window) to show up as the download source, but clicking the link / card takes you elsewhere.
Mardak
Apr 12, 2018
Member
Did design have any comments on what should show in the status bar on hover?
Did design have any comments on what should show in the status bar on hover?
| @@ -260,6 +274,18 @@ this.HighlightsFeed = class HighlightsFeed { | |||
| case at.UNINIT: | |||
| this.uninit(); | |||
| break; | |||
|
|
|||
| // Relay the downloads actions to DownloadsManager | |||
| case at.NEW_TAB_LOAD: | |||
k88hudson
Apr 10, 2018
Member
You don't need to do this, you can just call this.downloadsManager.onAction(action); on every action
You don't need to do this, you can just call this.downloadsManager.onAction(action); on every action
|
Chatted with Bryan, and the following changes are to be made:
|
|
Main question of how spread out all this download type checking should be.. in particular for Card component. But also any place with a type check, I wonder if it should be checking for behaviors or other properties instead. There's also a variety of cleanups and fixes. |
| # link that belongs to this downloaded item" | ||
| menu_action_copy_download_link=Copy Download link | ||
| menu_action_go_to_download_page=Go to Download page | ||
| menu_action_remove_download=Remove from history |
Mardak
Apr 12, 2018
Member
It would seem that being "consistent with our context menus" would mean these strings should be "Copy Download Link" "Go to Download Page" "Remove from History" etc… ?
It would seem that being "consistent with our context menus" would mean these strings should be "Copy Download Link" "Go to Download Page" "Remove from History" etc… ?
| // string for "Show in Finder". Else, show the regular hostname | ||
| if (this.props.link.type === "download") { | ||
| if (this.state.isHover) { | ||
| return <FormattedMessage id={GetPlatformString(this.props.platform)} defaultMessage={GetPlatformString("default")} />; |
Mardak
Apr 12, 2018
Member
This handling of hover with react seems a bit excessive? Could just always render both primary and alternate hostname texts and show / hide with css. Or maybe that's too complicated too ;)
<div class="card-host-name alternate">Show in Finder</div>
<div class="card-host-name">hostname</div>
.card-outer .card-host-name.alternate,
.card-outer:hover .alternate ~ .card-host-name { display: none; }
.card-outer:hover .card-host-name.alternate { display: block; }
This handling of hover with react seems a bit excessive? Could just always render both primary and alternate hostname texts and show / hide with css. Or maybe that's too complicated too ;)
<div class="card-host-name alternate">Show in Finder</div>
<div class="card-host-name">hostname</div>.card-outer .card-host-name.alternate,
.card-outer:hover .alternate ~ .card-host-name { display: none; }
.card-outer:hover .card-host-name.alternate { display: block; }
sarracini
Apr 12, 2018
Author
Contributor
Hmm, I suppose this is actually probably more reliable since we don't have to deal with react mouse over events blah blah blah
Hmm, I suppose this is actually probably more reliable since we don't have to deal with react mouse over events blah blah blah
| margin-top: 2px; | ||
|
|
||
| &.icon-download-folder { | ||
| background-size: $small-download-folder-icon-size; |
Mardak
Apr 12, 2018
Member
This can just be 100% and not need to explicitly set it for wider.
This can just be 100% and not need to explicitly set it for wider.
| @@ -129,6 +129,18 @@ | |||
| text-transform: uppercase; | |||
| } | |||
|
|
|||
| .card-download-icon { | |||
| float: right; | |||
Mardak
Apr 12, 2018
Member
This should be inline-end instead of right.
This should be inline-end instead of right.
|
|
||
| // Handle special case of default site | ||
| const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS; | ||
|
|
||
| const options = propOptions.map(o => LinkMenuOptions[o](site, index, source, isPrivateBrowsingEnabled, siteInfo)).map(option => { | ||
| const options = propOptions.map(o => LinkMenuOptions[o](site, index, source, isPrivateBrowsingEnabled, siteInfo, platform)).map(option => { |
Mardak
Apr 12, 2018
Member
Kinda looks like we just want to pass in props as the argument for each LinkMenuOptions so each can destructure its one parameter to pick out what is actually desired… eh
Kinda looks like we just want to pass in props as the argument for each LinkMenuOptions so each can destructure its one parameter to pick out what is actually desired… eh
| ChromeUtils.defineModuleGetter(this, "DownloadsCommon", | ||
| "resource:///modules/DownloadsCommon.jsm"); | ||
|
|
||
| const THIRTY_SIX_HOURS = 36 * 60 * 60 * 1000; |
Mardak
Apr 12, 2018
Member
Let's name this something more describing "recent download threshold" than just a text of the value.
Let's name this something more describing "recent download threshold" than just a text of the value.
| this.showFile(action.data.url); | ||
| break; | ||
| case at.OPEN_DOWNLOAD_FILE: | ||
| this.openFile(action.data.url); |
Mardak
Apr 12, 2018
Member
Eh.. these 5 actions could just just do something like
case OPEN:
downloadsCmd = "open";
break;
…
if (downloadsCmd) {
this.findDownloadedItem(action.data.url)[`downloadsCmd_${downloadsCmd}`]();
}
Too clever? ;)
Eh.. these 5 actions could just just do something like
case OPEN:
downloadsCmd = "open";
break;
…
if (downloadsCmd) {
this.findDownloadedItem(action.data.url)[`downloadsCmd_${downloadsCmd}`]();
}Too clever? ;)
sarracini
Apr 12, 2018
Author
Contributor
Yeah I thought about this too. I don't mind being too clever ;)
Yeah I thought about this too. I don't mind being too clever ;)
| <div className="card"> | ||
| {hasImage && <div className="card-preview-image-outer"> | ||
| <div className={`card-preview-image${this.state.imageLoaded ? " loaded" : ""}`} style={imageStyle} /> | ||
| </div>} | ||
| <div className={`card-details${hasImage ? "" : " no-image"}`}> | ||
| {link.hostname && <div className="card-host-name">{link.hostname}</div>} | ||
| {link.type === "download" && <div className="card-download-icon icon icon-download-folder" />} | ||
| {link.hostname && <div className="card-host-name">{this.computeHostname()}</div>} |
Mardak
Apr 12, 2018
Member
The Card component has been relatively generic allowing custom behavior via additional optional properties as opposed to type checking.
Alternatively, it looks like the Download Card looks and behaves a bit differently from existing Cards, so maybe it should just be its own Component?
The Card component has been relatively generic allowing custom behavior via additional optional properties as opposed to type checking.
Alternatively, it looks like the Download Card looks and behaves a bit differently from existing Cards, so maybe it should just be its own Component?
Mardak
Apr 12, 2018
Member
@k88hudson any thoughts on if we should care about how generic Card is? It also would potentially affect web extensions that happen to have use a "download" type.. and more generally of special type behaviors.
@k88hudson any thoughts on if we should care about how generic Card is? It also would potentially affect web extensions that happen to have use a "download" type.. and more generally of special type behaviors.
| @@ -313,7 +313,9 @@ class PlacesFeed { | |||
| this.saveToPocket(action.data.site, action._target.browser); | |||
| break; | |||
| case at.OPEN_LINK: { | |||
| this.openLink(action); | |||
| if (action.data.type !== "download") { | |||
Mardak
Apr 12, 2018
Member
This check / change shouldn't be needed anymore.
This check / change shouldn't be needed anymore.
| const downloadedItem = element.download; | ||
| return { | ||
| hostname: new URL(downloadedItem.source.url).hostname, | ||
| url: downloadedItem.source.url, |
Mardak
Apr 12, 2018
Member
Did design have any comments on what should show in the status bar on hover?
Did design have any comments on what should show in the status bar on hover?
|
A note for myself: Make sure the private browsing downloads don't show up |
|
Also @Mardak design said they didn't care about how the status bar shows the url |
5879c98
to
2b9a547
|
Recent discoveries blocking this bug:
|
4719ff6
to
4edbf4d
c1c7763
to
a71705a
| })); | ||
| } else { | ||
| const {altKey, button, ctrlKey, metaKey, shiftKey} = event; | ||
| this.props.dispatch(ac.AlsoToMain({ |
Mardak
Apr 18, 2018
Member
nit: we can fix this up too to Only
nit: we can fix this up too to Only
| @@ -146,6 +154,8 @@ export class Card extends React.PureComponent { | |||
| <div className={`card-preview-image${this.state.imageLoaded ? " loaded" : ""}`} style={imageStyle} /> | |||
| </div>} | |||
| <div className={`card-details${hasImage ? "" : " no-image"}`}> | |||
| {link.type === "download" && <div className="card-download-icon icon icon-download-folder" />} | |||
| {link.type === "download" && <div className="card-host-name alternate"><FormattedMessage id={GetPlatformString(this.props.platform)} defaultMessage={GetPlatformString("default")} /></div>} | |||
Mardak
Apr 18, 2018
Member
Do we need a defaultMessage? It would only be used if the provided id is missing? Our string packaging will fall back to english or something if a locale didn't translate a string. I suppose it could be possible that we remove a string and it'll switch to the default maybe.. but that should probably be more visible anyway?
Do we need a defaultMessage? It would only be used if the provided id is missing? Our string packaging will fall back to english or something if a locale didn't translate a string. I suppose it could be possible that we remove a string and it'll switch to the default maybe.. but that should probably be more visible anyway?
sarracini
Apr 19, 2018
Author
Contributor
Yup we can remove this, I think it was laying around from before when I didn't have it always showing an alternate host 👍
Yup we can remove this, I think it was laying around from before when I didn't have it always showing an alternate host
| @@ -22,6 +22,7 @@ type_label_visited=Visited | |||
| type_label_bookmarked=Bookmarked | |||
| type_label_recommended=Trending | |||
| type_label_pocket=Saved to Pocket | |||
| type_label_download=Downloaded | |||
Mardak
Apr 18, 2018
Member
nit: we have type_label_bookmarked so let's do "downloaded" in the id.
nit: we have type_label_bookmarked so let's do "downloaded" in the id.
| @@ -49,6 +49,9 @@ | |||
| .card-title { | |||
| color: var(--newtab-link-primary-color); | |||
| } | |||
|
|
|||
| .alternate ~ .card-host-name { display: none; } | |||
| .card-host-name.alternate { display: block; } | |||
Mardak
Apr 18, 2018
Member
nit: bracing new lines
nit: bracing new lines
| downloadsCmd = "copyLocation"; | ||
| break; | ||
| case at.GO_TO_DOWNLOAD_PAGE: | ||
| downloadsCmd = "openReferrer"; |
Mardak
Apr 18, 2018
Member
This seems to trigger an exception this.element.ownerGlobal.openURL is not a function DownloadsViewUI.jsm:453
https://searchfox.org/mozilla-central/rev/59a9a86553e9bfd9277202748ff791fd9bc0713b/browser/components/downloads/DownloadsViewUI.jsm#452-453
This seems to trigger an exception this.element.ownerGlobal.openURL is not a function DownloadsViewUI.jsm:453
https://searchfox.org/mozilla-central/rev/59a9a86553e9bfd9277202748ff791fd9bc0713b/browser/components/downloads/DownloadsViewUI.jsm#452-453
| const downloadedItem = elem.download; | ||
| if (!this._viewableDownloadItems.has(downloadedItem.source.url)) { | ||
| this._viewableDownloadItems.set(downloadedItem.source.url, elem); | ||
| this._store.dispatch({type: at.DOWNLOAD_CHANGED}); |
| downloadsCmd = "openReferrer"; | ||
| break; | ||
| case at.REMOVE_DOWNLOAD_FILE: | ||
| downloadsCmd = "delete"; |
Mardak
Apr 18, 2018
Member
Looks like it's possible for this to throw if the file was deleted.. although not sure why it doesn't show up as disabled?
[Exception... "Component returned failure code: 0x80520006 (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) [nsIFile.isExecutable]" nsresult: "0x80520006 (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)" location: "JS frame :: resource:///modules/DownloadsCommon.jsm :: openDownloadedFile :: line 423" data: no]
Looks like it's possible for this to throw if the file was deleted.. although not sure why it doesn't show up as disabled?
[Exception... "Component returned failure code: 0x80520006 (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) [nsIFile.isExecutable]" nsresult: "0x80520006 (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)" location: "JS frame :: resource:///modules/DownloadsCommon.jsm :: openDownloadedFile :: line 423" data: no]
| if (downloadsCmd) { | ||
| let elem = this._viewableDownloadItems.get(action.data.url); | ||
| if (elem) { | ||
| elem[`downloadsCmd_${downloadsCmd}`](); |
Mardak
Apr 18, 2018
Member
I've learned that dynamically building keys for strings, methods, etc. is nice looking but makes it hard to find, e.g., from searchfox. "Are there any more callers of downloadsCmd_delete? No? Baleeted!" Sorry for giving the example with string building in the previous comment. :(
I've learned that dynamically building keys for strings, methods, etc. is nice looking but makes it hard to find, e.g., from searchfox. "Are there any more callers of downloadsCmd_delete? No? Baleeted!" Sorry for giving the example with string building in the previous comment. :(
sarracini
Apr 19, 2018
Author
Contributor
😂 no worries!
|
Latest changes doesn't fix the whole deleted from system still showing up in highlights situation, but addresses all the other changes |
| const elem = new DownloadElement(download, this._browser); | ||
| const downloadedItem = elem.download; | ||
| if (!this._viewableDownloadItems.has(downloadedItem.source.url)) { | ||
| if (downloadedItem.succeeded && downloadedItem.target.exists) { |
Mardak
Apr 19, 2018
Member
Between these two if blocks, add await downloadedItem.refresh(); and that should get us the correct exists value.
Between these two if blocks, add await downloadedItem.refresh(); and that should get us the correct exists value.
Mardak
Apr 19, 2018
Member
Probably add a comment too about why
Probably add a comment too about why
| get downloads() { | ||
| let results = []; | ||
| for (const url of this._viewableDownloadItems.keys()) { | ||
| const elem = this._viewableDownloadItems.get(url); |
Mardak
Apr 19, 2018
Member
nit: for (const elem of .values()) as we don't use the url
nit: for (const elem of .values()) as we don't use the url
|
Let's fix the download exists stuff and some more cleaning, and this should be good! |
| copyLocation(url) { | ||
| let elem = this._viewableDownloadItems.get(url); | ||
| if (elem && elem.download.target.exists) { | ||
| elem.downloadsCmd_copyLocation(); |
Mardak
Apr 19, 2018
Member
As noted on irc, we can do the method strings just not string concatenation. And we shouldn't need a separate exists check. If the user does delete the file while the card is already showing... oh well?
As noted on irc, we can do the method strings just not string concatenation. And we shouldn't need a separate exists check. If the user does delete the file while the card is already showing... oh well?
| // If we only care about downloads where the file still exists, refresh them | ||
| // to ensure the 'exists' attribute is up to date | ||
| if (onlyExists) { | ||
| await elem.download.refresh(); |
Mardak
Apr 19, 2018
Member
As discussed on irc, I'm assuming refresh, which results in file IO, is significantly slower than anything else we do in this method whether that's iterating or sorting, so we want to minimize the number of times we call it. This can be done by only checking the first few most recent downloads in order, so if the first one exists, we're done.
As discussed on irc, I'm assuming refresh, which results in file IO, is significantly slower than anything else we do in this method whether that's iterating or sorting, so we want to minimize the number of times we call it. This can be done by only checking the first few most recent downloads in order, so if the first one exists, we're done.
| results.push(formattedDownloadForHighlights); | ||
| for (const elem of this._viewableDownloadItems.values()) { | ||
| // Only get downloads within the time threshold specified | ||
| if (elem && (elem.downloadedSince < threshold)) { |
Mardak
Apr 19, 2018
•
Member
Also just documenting from irc. elem shouldn't ever be falsey now. And this comparison expands to Date.now() - endTime < threshold and gets computed for every download. We can shuffle things around to be Date.now() - threshold < endTime which makes the math loop invariant(-ish), so compute a timeThreshold = Date.now() - threshold once before the loop and check endTime > timeThreshold
Also just documenting from irc. elem shouldn't ever be falsey now. And this comparison expands to Date.now() - endTime < threshold and gets computed for every download. We can shuffle things around to be Date.now() - threshold < endTime which makes the math loop invariant(-ish), so compute a timeThreshold = Date.now() - threshold once before the loop and check endTime > timeThreshold
| if (onlyExists) { | ||
| // Refresh download to ensure the 'exists' attribute is up to date | ||
| await elem.download.refresh(); | ||
| if (elem.download.target.exists && elem.download.succeeded) { |
Mardak
Apr 19, 2018
Member
you can if (!exists) continue; and share the formatDownload / else stuff. Both paths want only succeeded downloads too?
you can if (!exists) continue; and share the formatDownload / else stuff. Both paths want only succeeded downloads too?
sarracini
Apr 19, 2018
Author
Contributor
maybe pass succeeded as a parameter too?
maybe pass succeeded as a parameter too?
Mardak
Apr 19, 2018
Member
I suppose an optional succeeded parameter would match and doesn't need to add an extra branch for test coverage
I suppose an optional succeeded parameter would match and doesn't need to add an extra branch for test coverage
|
Should be good with tests passing! |
| if (onlyExists) { | ||
| // Refresh download to ensure the 'exists' attribute is up to date | ||
| await elem.download.refresh(); | ||
| if (elem.download.target.exists && elem.download.succeeded) { |
Mardak
Apr 19, 2018
Member
I suppose an optional succeeded parameter would match and doesn't need to add an extra branch for test coverage
I suppose an optional succeeded parameter would match and doesn't need to add an extra branch for test coverage
| } | ||
|
|
||
| downloadsCmd_openReferrer() { | ||
| this.element.openNewTabWith(this.download.source.referrer); |
Mardak
Apr 19, 2018
Member
Looks like this opens in a background tab. Maybe just pass in true as the second argument for shifted?
Looks like this opens in a background tab. Maybe just pass in true as the second argument for shifted?
| if (!this._viewableDownloadItems.has(downloadedItem.source.url)) { | ||
| this._viewableDownloadItems.set(downloadedItem.source.url, elem); | ||
|
|
||
| // Debounce incoming onDownloadAdded notifications |
Mardak
Apr 19, 2018
Member
Probably note this is for startup where all existing downloads are added
Probably note this is for startup where all existing downloads are added
|
|
||
| // Only get downloads within the time threshold specified and sort by recency | ||
| let downloads = [...this._viewableDownloadItems.values()] | ||
| .filter(elem => elem.download.endTime > Date.now() - threshold) |
Mardak
Apr 19, 2018
Member
nit: Date.now() - threshold defined outside of the callback.
nit: Date.now() - threshold defined outside of the callback.
| } | ||
| } | ||
|
|
||
| async getDownloads({threshold, numItems = this._viewableDownloadItems.size, onlyExists = false}) { |
Mardak
Apr 19, 2018
Member
If threshold is required, probably make it as the first parameter and have the second the options object
If threshold is required, probably make it as the first parameter and have the second the options object
| return results; | ||
| } | ||
|
|
||
| uninit() { |
Mardak
Apr 19, 2018
Member
Probably want to cancel the timer to be clean
Probably want to cancel the timer to be clean
| type: at.SHOW_DOWNLOAD_FILE, | ||
| data: {url: site.url} | ||
| }), | ||
| disabled: !site.path |
Mardak
Apr 19, 2018
Member
I think we don't need this disabled anymore? Same for OpenFile
I think we don't need this disabled anymore? Same for OpenFile




Adds a single, most recent download to highlights. Right now just appends it to the end of all highlights, so the easiest way to test this is on a new profile, with little history and maybe just a bookmark.
Some notes:
Designs: https://mozilla.invisionapp.com/share/J6GEU4EHTYC#/screens
Here's a picture!
