Merge pull request #8009 from atom/jl-ns-battery-life

Change Document Polling from Time Interval to Event
This commit is contained in:
Nathan Sobo
2015-07-23 13:06:02 -06:00
6 changed files with 95 additions and 67 deletions

View File

@@ -280,6 +280,7 @@ describe "TextEditorComponent", ->
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)'
atom.views.performDocumentPoll()
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
@@ -701,7 +702,8 @@ describe "TextEditorComponent", ->
# favor gutter color if it's assigned
gutterNode.style.backgroundColor = 'rgb(255, 0, 0)'
advanceClock(atom.views.documentPollingInterval)
atom.views.performDocumentPoll()
nextAnimationFrame()
expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in lineNumbersNode.querySelectorAll(".tile")
@@ -800,6 +802,7 @@ describe "TextEditorComponent", ->
describe "when the component is destroyed", ->
it "stops listening for folding events", ->
nextAnimationFrame()
component.destroy()
lineNumber = component.lineNumberNodeForScreenRow(1)
@@ -824,6 +827,8 @@ describe "TextEditorComponent", ->
expect(lineNumberHasClass(1, 'folded')).toBe false
it "does not fold when the line number componentNode is clicked", ->
nextAnimationFrame() # clear pending frame request
lineNumber = component.lineNumberNodeForScreenRow(1)
lineNumber.dispatchEvent(buildClickEvent(lineNumber))
expect(nextAnimationFrame).toBe noAnimationFrame
@@ -2600,7 +2605,7 @@ describe "TextEditorComponent", ->
expect(componentNode.querySelectorAll('.line').length).toBe 0
hiddenParent.style.display = 'block'
advanceClock(atom.views.documentPollingInterval)
atom.views.performDocumentPoll()
expect(componentNode.querySelectorAll('.line').length).toBeGreaterThan 0
@@ -2710,13 +2715,13 @@ describe "TextEditorComponent", ->
expect(parseInt(newHeight)).toBeLessThan wrapperNode.offsetHeight
wrapperNode.style.height = newHeight
advanceClock(atom.views.documentPollingInterval)
atom.views.performDocumentPoll()
nextAnimationFrame()
expect(componentNode.querySelectorAll('.line')).toHaveLength(6)
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
advanceClock(atom.views.documentPollingInterval)
atom.views.performDocumentPoll()
nextAnimationFrame()
expect(componentNode.querySelector('.line').textContent).toBe "var quicksort "
@@ -2725,7 +2730,7 @@ describe "TextEditorComponent", ->
scrollViewNode.style.paddingLeft = 20 + 'px'
componentNode.style.width = 30 * charWidth + 'px'
advanceClock(atom.views.documentPollingInterval)
atom.views.performDocumentPoll()
nextAnimationFrame()
expect(component.lineNodeForScreenRow(0).textContent).toBe "var quicksort = "

View File

@@ -7,6 +7,9 @@ describe "ViewRegistry", ->
beforeEach ->
registry = new ViewRegistry
afterEach ->
registry.clearDocumentRequests()
describe "::getView(object)", ->
describe "when passed a DOM node", ->
it "returns the given DOM node", ->
@@ -158,13 +161,13 @@ describe "ViewRegistry", ->
registry.updateDocument -> events.push('write')
registry.readDocument -> events.push('read')
advanceClock(registry.documentPollingInterval)
window.dispatchEvent(new UIEvent('resize'))
expect(events).toEqual []
frameRequests[0]()
expect(events).toEqual ['write', 'read', 'poll']
advanceClock(registry.documentPollingInterval)
window.dispatchEvent(new UIEvent('resize'))
expect(events).toEqual ['write', 'read', 'poll', 'poll']
it "polls the document after updating when ::pollAfterNextUpdate() has been called", ->
@@ -183,25 +186,51 @@ describe "ViewRegistry", ->
expect(events).toEqual ['write', 'read', 'poll']
describe "::pollDocument(fn)", ->
it "calls all registered reader functions on an interval until they are disabled via a returned disposable", ->
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
[testElement, testStyleSheet, disposable1, disposable2, events] = []
beforeEach ->
testElement = document.createElement('div')
testStyleSheet = document.createElement('style')
testStyleSheet.textContent = 'body {}'
jasmineContent = document.getElementById('jasmine-content')
jasmineContent.appendChild(testElement)
jasmineContent.appendChild(testStyleSheet)
events = []
disposable1 = registry.pollDocument -> events.push('poll 1')
disposable2 = registry.pollDocument -> events.push('poll 2')
it "calls all registered polling functions after document or stylesheet changes until they are disabled via a returned disposable", ->
jasmine.useRealClock()
expect(events).toEqual []
advanceClock(registry.documentPollingInterval)
testElement.style.width = '400px'
waitsFor "events to occur in response to DOM mutation", -> events.length > 0
runs ->
expect(events).toEqual ['poll 1', 'poll 2']
events.length = 0
testStyleSheet.textContent = 'body {color: #333;}'
waitsFor "events to occur in reponse to style sheet mutation", -> events.length > 0
runs ->
expect(events).toEqual ['poll 1', 'poll 2']
events.length = 0
disposable1.dispose()
testElement.style.color = '#fff'
waitsFor "more events to occur in response to DOM mutation", -> events.length > 0
runs ->
expect(events).toEqual ['poll 2']
it "calls all registered polling functions when the window resizes", ->
expect(events).toEqual []
window.dispatchEvent(new UIEvent('resize'))
expect(events).toEqual ['poll 1', 'poll 2']
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2']
disposable1.dispose()
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2']
disposable2.dispose()
advanceClock(registry.documentPollingInterval)
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2']

View File

@@ -107,7 +107,6 @@ class TextEditorComponent
@disposables.dispose()
@presenter.destroy()
@gutterContainerComponent?.destroy()
window.removeEventListener 'resize', @requestHeightAndWidthMeasurement
getDomNode: ->
@domNode
@@ -224,7 +223,6 @@ class TextEditorComponent
@domNode.addEventListener 'textInput', @onTextInput
@scrollViewNode.addEventListener 'mousedown', @onMouseDown
@scrollViewNode.addEventListener 'scroll', @onScrollViewScroll
window.addEventListener 'resize', @requestHeightAndWidthMeasurement
@listenForIMEEvents()
@trackSelectionClipboard() if process.platform is 'linux'
@@ -589,15 +587,6 @@ class TextEditorComponent
else
@wasVisible = false
requestHeightAndWidthMeasurement: =>
return if @heightAndWidthMeasurementRequested
@heightAndWidthMeasurementRequested = true
requestAnimationFrame =>
@heightAndWidthMeasurementRequested = false
@measureDimensions()
@measureWindowSize()
# Measure explicitly-styled height and width and relay them to the model. If
# these values aren't explicitly styled, we assume the editor is unconstrained
# and use the scrollHeight / scrollWidth as its height and width in

View File

@@ -1386,12 +1386,15 @@ class TextEditorPresenter
@emitDidUpdateState()
startBlinkingCursors: ->
unless @toggleCursorBlinkHandle
unless @isCursorBlinking()
@state.content.cursorsVisible = true
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink.bind(this), @getCursorBlinkPeriod() / 2)
isCursorBlinking: ->
@toggleCursorBlinkHandle?
stopBlinkingCursors: (visible) ->
if @toggleCursorBlinkHandle
if @isCursorBlinking()
@state.content.cursorsVisible = visible
clearInterval(@toggleCursorBlinkHandle)
@toggleCursorBlinkHandle = null
@@ -1401,7 +1404,8 @@ class TextEditorPresenter
@emitDidUpdateState()
pauseCursorBlinking: ->
@stopBlinkingCursors(true)
@startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
@startBlinkingCursorsAfterDelay()
@emitDidUpdateState()
if @isCursorBlinking()
@stopBlinkingCursors(true)
@startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
@startBlinkingCursorsAfterDelay()
@emitDidUpdateState()

View File

@@ -1,6 +1,7 @@
{find} = require 'underscore-plus'
Grim = require 'grim'
{Disposable} = require 'event-kit'
_ = require 'underscore-plus'
# Essential: `ViewRegistry` handles the association between model and view
# types in Atom. We call this association a View Provider. As in, for a given
@@ -42,11 +43,11 @@ Grim = require 'grim'
# ```
module.exports =
class ViewRegistry
documentPollingInterval: 200
documentUpdateRequested: false
documentReadInProgress: false
performDocumentPollAfterUpdate: false
pollIntervalHandle: null
debouncedPerformDocumentPoll: null
minimumPollInterval: 200
constructor: ->
@views = new WeakMap
@@ -55,6 +56,9 @@ class ViewRegistry
@documentReaders = []
@documentPollers = []
@observer = new MutationObserver(@requestDocumentPoll)
@debouncedPerformDocumentPoll = _.throttle(@performDocumentPoll, @minimumPollInterval).bind(this)
# Essential: Add a provider that will be used to construct views in the
# workspace's view layer based on model objects in its model layer.
#
@@ -208,14 +212,19 @@ class ViewRegistry
writer() while writer = @documentWriters.shift()
startPollingDocument: ->
@pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval)
window.addEventListener('resize', @requestDocumentPoll)
@observer.observe(document, {subtree: true, childList: true, attributes: true})
stopPollingDocument: ->
window.clearInterval(@pollIntervalHandle)
window.removeEventListener('resize', @requestDocumentPoll)
@observer.disconnect()
performDocumentPoll: =>
requestDocumentPoll: =>
if @documentUpdateRequested
@performDocumentPollAfterUpdate = true
else
poller() for poller in @documentPollers
return
@debouncedPerformDocumentPoll()
performDocumentPoll: ->
poller() for poller in @documentPollers
return

View File

@@ -12,7 +12,6 @@ class WorkspaceElement extends HTMLElement
createdCallback: ->
@subscriptions = new CompositeDisposable
@initializeGlobalTextEditorStyleSheet()
@initializeContent()
@observeScrollbarStyle()
@observeTextEditorFontConfig()
@@ -26,10 +25,6 @@ class WorkspaceElement extends HTMLElement
@subscriptions.dispose()
@model.destroy()
initializeGlobalTextEditorStyleSheet: ->
atom.styles.addStyleSheet('atom-text-editor {}', sourcePath: 'global-text-editor-styles')
@globalTextEditorStyleSheet = document.head.querySelector('style[source-path="global-text-editor-styles"]').sheet
initializeContent: ->
@classList.add 'workspace'
@setAttribute 'tabindex', -1
@@ -54,9 +49,20 @@ class WorkspaceElement extends HTMLElement
@classList.add("scrollbars-visible-when-scrolling")
observeTextEditorFontConfig: ->
@subscriptions.add atom.config.observe 'editor.fontSize', @setTextEditorFontSize.bind(this)
@subscriptions.add atom.config.observe 'editor.fontFamily', @setTextEditorFontFamily.bind(this)
@subscriptions.add atom.config.observe 'editor.lineHeight', @setTextEditorLineHeight.bind(this)
@updateGlobalTextEditorStyleSheet()
@subscriptions.add atom.config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add atom.config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add atom.config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
updateGlobalTextEditorStyleSheet: ->
styleSheetSource = """
atom-text-editor {
font-size: #{atom.config.get('editor.fontSize')}px;
font-family: #{atom.config.get('editor.fontFamily')};
line-height: #{atom.config.get('editor.lineHeight')};
}
"""
atom.styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')
createSpacePenShim: ->
WorkspaceView ?= require './workspace-view'
@@ -87,20 +93,6 @@ class WorkspaceElement extends HTMLElement
getModel: -> @model
setTextEditorFontSize: (fontSize) ->
@updateGlobalEditorStyle('font-size', fontSize + 'px')
setTextEditorFontFamily: (fontFamily) ->
@updateGlobalEditorStyle('font-family', fontFamily)
setTextEditorLineHeight: (lineHeight) ->
@updateGlobalEditorStyle('line-height', lineHeight)
updateGlobalEditorStyle: (property, value) ->
editorRule = @globalTextEditorStyleSheet.cssRules[0]
editorRule.style[property] = value
atom.themes.emitter.emit 'did-update-stylesheet', @globalTextEditorStyleSheet
handleFocus: (event) ->
@model.getActivePane().activate()