aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode_insert.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'content_scripts/mode_insert.coffee')
-rw-r--r--content_scripts/mode_insert.coffee83
1 files changed, 83 insertions, 0 deletions
diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee
new file mode 100644
index 00000000..196f910b
--- /dev/null
+++ b/content_scripts/mode_insert.coffee
@@ -0,0 +1,83 @@
+
+class InsertMode extends Mode
+ # There is one permanently-installed instance of InsertMode. It tracks focus changes and
+ # activates/deactivates itself (by setting @insertModeLock) accordingly.
+ @permanentInstance: null
+
+ constructor: (options = {}) ->
+ InsertMode.permanentInstance ||= @
+ @permanent = (@ == InsertMode.permanentInstance)
+
+ # If truthy, then we were activated by the user (with "i").
+ @global = options.global
+
+ handleKeyEvent = (event) =>
+ return @continueBubbling unless @isActive event
+ return @stopBubblingAndTrue unless event.type == 'keydown' and KeyboardUtils.isEscape event
+ DomUtils.suppressKeyupAfterEscape handlerStack
+ @exit event, event.srcElement
+ @suppressEvent
+
+ defaults =
+ name: "insert"
+ keypress: handleKeyEvent
+ keyup: handleKeyEvent
+ keydown: handleKeyEvent
+
+ super extend defaults, options
+
+ @insertModeLock =
+ if document.activeElement and DomUtils.isEditable document.activeElement
+ # An input element is already active, so use it.
+ document.activeElement
+ else
+ null
+
+ @push
+ "blur": (event) => @alwaysContinueBubbling =>
+ target = event.target
+ # We can't rely on focus and blur events arriving in the expected order. When the active element
+ # changes, we might get "focus" before "blur". We track the active element in @insertModeLock, and
+ # exit only when that element blurs.
+ @exit event, target if @insertModeLock and target == @insertModeLock
+ "focus": (event) => @alwaysContinueBubbling =>
+ if @insertModeLock != event.target and DomUtils.isFocusable event.target
+ @activateOnElement event.target
+
+ isActive: (event) ->
+ return false if event == InsertMode.suppressedEvent
+ return true if @insertModeLock or @global
+ # Some sites (e.g. inbox.google.com) change the contentEditable property on the fly (see #1245); and
+ # unfortunately, the focus event fires *before* the change. Therefore, we need to re-check whether the
+ # active element is contentEditable.
+ @activateOnElement document.activeElement if document.activeElement?.isContentEditable
+ @insertModeLock != null
+
+ activateOnElement: (element) ->
+ @log "#{@id}: activating (permanent)" if @debug and @permanent
+ @insertModeLock = element
+ Mode.updateBadge()
+
+ exit: (_, target) ->
+ # Note: target == undefined, here, is required only for tests.
+ if (target and target == @insertModeLock) or @global or target == undefined
+ @log "#{@id}: deactivating (permanent)" if @debug and @permanent and @insertModeLock
+ @insertModeLock = null
+ if target and DomUtils.isFocusable target
+ # Remove the focus, so the user can't just get back into insert mode by typing in the same input box.
+ # NOTE(smblott, 2014/12/22) Including embeds for .blur() etc. here is experimental. It appears to be
+ # the right thing to do for most common use cases. However, it could also cripple flash-based sites and
+ # games. See discussion in #1211 and #1194.
+ target.blur()
+ # Exit, but only if this isn't the permanently-installed instance.
+ if @permanent then Mode.updateBadge() else super()
+
+ updateBadge: (badge) ->
+ badge.badge ||= "I" if @isActive badge
+
+ # Static stuff. This allows PostFindMode to suppress the permanently-installed InsertMode instance.
+ @suppressedEvent: null
+ @suppressEvent: (event) -> @suppressedEvent = event
+
+root = exports ? window
+root.InsertMode = InsertMode