From ac51207860a10714af47a649ec6d654ea9ac05c6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 8 Dec 2014 21:06:23 -0800 Subject: [PATCH] Optimized the handling of webview attribute mutation Imported from: https://chromium.googlesource.com/chromium/src/+/86dff6fc519e5db4469d89b2f56f8a1ce36d864f%5E%21/ --- .../lib/web-view/web-view-attributes.coffee | 120 +++++++++++++++--- atom/renderer/lib/web-view/web-view.coffee | 119 ++--------------- 2 files changed, 110 insertions(+), 129 deletions(-) diff --git a/atom/renderer/lib/web-view/web-view-attributes.coffee b/atom/renderer/lib/web-view/web-view-attributes.coffee index da34a0d49b5f..cf1c063ec81b 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.coffee +++ b/atom/renderer/lib/web-view/web-view-attributes.coffee @@ -1,4 +1,5 @@ WebView = require './web-view' +guestViewInternal = require './guest-view-internal' webViewConstants = require './web-view-constants' # Attribute objects. @@ -7,7 +8,9 @@ class WebViewAttribute constructor: (name, webViewImpl) -> @name = name @webViewImpl = webViewImpl - @ignoreNextMutation = false + @ignoreMutation = false + + @defineProperty() # Retrieves and returns the attribute's value. getValue: -> @webViewImpl.webviewNode.getAttribute(@name) || '' @@ -15,8 +18,14 @@ class WebViewAttribute # Sets the attribute's value. setValue: (value) -> @webViewImpl.webviewNode.setAttribute(@name, value || '') + # Changes the attribute's value without triggering its mutation handler. + setValueIgnoreMutation: (value) -> + @ignoreMutation = true + @webViewImpl.webviewNode.setAttribute(@name, value || '') + @ignoreMutation = false + # Defines this attribute as a property on the webview node. - define: -> + defineProperty: -> Object.defineProperty @webViewImpl.webviewNode, @name, get: => @getValue() set: (value) => @setValue value @@ -30,8 +39,7 @@ class BooleanAttribute extends WebViewAttribute constructor: (name, webViewImpl) -> super name, webViewImpl - getValue: -> - @webViewImpl.webviewNode.hasAttribute @name + getValue: -> @webViewImpl.webviewNode.hasAttribute @name setValue: (value) -> unless value @@ -39,8 +47,42 @@ class BooleanAttribute extends WebViewAttribute else @webViewImpl.webviewNode.setAttribute @name, '' +# Attribute that specifies whether transparency is allowed in the webview. +class AllowTransparencyAttribute extends BooleanAttribute + constructor: (webViewImpl) -> + super webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl + + handleMutation: (oldValue, newValue) -> + return unless @webViewImpl.guestInstanceId + guestViewInternal.setAllowTransparency @webViewImpl.guestInstanceId, @webViewImpl.attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY].getValue() + +# Attribute used to define the demension limits of autosizing. +class AutosizeDimensionAttribute extends WebViewAttribute + constructor: (name, webViewImpl) -> + super name, webViewImpl + + getValue: -> parseInt(@webViewImpl.webviewNode.getAttribute(@name)) || 0 + + handleMutation: (oldValue, newValue) -> + return unless @webViewImpl.guestInstanceId + guestViewInternal.setAutoSize @webViewImpl.guestInstanceId, + enableAutoSize: @webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() + min: + width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0 + height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0 + max: + width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0 + height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0 + +# Attribute that specifies whether the webview should be autosized. +class AutosizeAttribute extends BooleanAttribute + constructor: (webViewImpl) -> + super webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl + + handleMutation: AutosizeDimensionAttribute::handleMutation + # Attribute representing the state of the storage partition. -class Partition extends WebViewAttribute +class PartitionAttribute extends WebViewAttribute constructor: (webViewImpl) -> super webViewConstants.ATTRIBUTE_PARTITION, webViewImpl @validPartitionId = true @@ -51,35 +93,71 @@ class Partition extends WebViewAttribute # The partition cannot change if the webview has already navigated. unless @webViewImpl.beforeFirstNavigation window.console.error webViewConstants.ERROR_MSG_ALREADY_NAVIGATED - @ignoreNextMutation = true - @webViewImpl.webviewNode.setAttribute @name, oldValue + @setValueIgnoreMutation oldValue return if newValue is 'persist:' @validPartitionId = false window.console.error webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE +# Attribute that handles the location and navigation of the webview. +class SrcAttribute extends WebViewAttribute + constructor: (webViewImpl) -> + super webViewConstants.ATTRIBUTE_SRC, webViewImpl + @setupMutationObserver() + + handleMutation: (oldValue, newValue) -> + # Once we have navigated, we don't allow clearing the src attribute. + # Once enters a navigated state, it cannot return to a + # placeholder state. + if not newValue and oldValue + # src attribute changes normally initiate a navigation. We suppress + # the next src attribute handler call to avoid reloading the page + # on every guest-initiated navigation. + @setValueIgnoreMutation oldValue + return + @webViewImpl.parseSrcAttribute() + + # The purpose of this mutation observer is to catch assignment to the src + # attribute without any changes to its value. This is useful in the case + # where the webview guest has crashed and navigating to the same address + # spawns off a new process. + setupMutationObserver: -> + @observer = new MutationObserver (mutations) => + for mutation in mutations + oldValue = mutation.oldValue + newValue = @getValue() + return if oldValue isnt newValue + @handleMutation oldValue, newValue + params = + attributes: true, + attributeOldValue: true, + attributeFilter: [@name] + @observer.observe @webViewImpl.webviewNode, params + +# Attribute specifies HTTP referrer. +class HttpReferrerAttribute extends WebViewAttribute + constructor: (webViewImpl) -> + super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl + + handleMutation: (oldValue, newValue) -> + @webViewImpl.parseSrcAttribute() + # Sets up all of the webview attributes. WebView::setupWebViewAttributes = -> @attributes = {} - # Initialize the attributes with special behavior (and custom attribute - # objects). - @attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = - new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, this) - @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = - new BooleanAttribute(webViewConstants.ATTRIBUTE_AUTOSIZE, this) - @attributes[webViewConstants.ATTRIBUTE_PARTITION] = new Partition(this) + @attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this) + @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this) + @attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) + @attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) + @attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) - # Initialize the remaining attributes, which have default behavior. - defaultAttributes = [ + autosizeAttributes = [ webViewConstants.ATTRIBUTE_MAXHEIGHT webViewConstants.ATTRIBUTE_MAXWIDTH webViewConstants.ATTRIBUTE_MINHEIGHT webViewConstants.ATTRIBUTE_MINWIDTH - webViewConstants.ATTRIBUTE_SRC - webViewConstants.ATTRIBUTE_HTTPREFERRER ] - - for attribute in defaultAttributes - @attributes[attribute] = new WebViewAttribute(attribute, this) + for attribute in autosizeAttributes + @attributes[attribute] = new AutosizeDimensionAttribute(attribute, this) diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index 91a85a8cd78f..fd3984f64f2a 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -4,15 +4,6 @@ webViewConstants = require './web-view-constants' webFrame = require 'web-frame' remote = require 'remote' -# Attributes. -AUTO_SIZE_ATTRIBUTES = [ - webViewConstants.ATTRIBUTE_AUTOSIZE, - webViewConstants.ATTRIBUTE_MAXHEIGHT, - webViewConstants.ATTRIBUTE_MAXWIDTH, - webViewConstants.ATTRIBUTE_MINHEIGHT, - webViewConstants.ATTRIBUTE_MINWIDTH, -] - # ID generator. nextId = 0 getNextId = -> ++nextId @@ -34,13 +25,11 @@ class WebView @browserPluginNode = @createBrowserPluginNode() shadowRoot = @webviewNode.createShadowRoot() @setupWebViewAttributes() - @setupWebViewSrcAttributeMutationObserver() @setupFocusPropagation() @setupWebviewNodeProperties() @viewInstanceId = getNextId() - # UPSTREAM: new WebViewEvents(this, this.viewInstanceId); guestViewInternal.registerEvents this, @viewInstanceId shadowRoot.appendChild @browserPluginNode @@ -95,9 +84,6 @@ class WebView throw new Error(webViewConstants.ERROR_MSG_CANNOT_INJECT_SCRIPT) unless @guestInstanceId setupWebviewNodeProperties: -> - for attributeName of @attributes - @attributes[attributeName].define() - # We cannot use {writable: true} property descriptor because we want a # dynamic getter value. Object.defineProperty @webviewNode, 'contentWindow', @@ -107,94 +93,17 @@ class WebView # No setter. enumerable: true - # The purpose of this mutation observer is to catch assignment to the src - # attribute without any changes to its value. This is useful in the case - # where the webview guest has crashed and navigating to the same address - # spawns off a new process. - setupWebViewSrcAttributeMutationObserver: -> - @srcAndPartitionObserver = new MutationObserver (mutations) => - for mutation in mutations - oldValue = mutation.oldValue - newValue = @attributes[mutation.attributeName].getValue() - return if oldValue isnt newValue - @handleWebviewAttributeMutation mutation.attributeName, oldValue, newValue - params = - attributes: true, - attributeOldValue: true, - attributeFilter: [ - webViewConstants.ATTRIBUTE_SRC - webViewConstants.ATTRIBUTE_PARTITION - webViewConstants.ATTRIBUTE_HTTPREFERRER - ] - @srcAndPartitionObserver.observe @webviewNode, params - # This observer monitors mutations to attributes of the and # updates the BrowserPlugin properties accordingly. In turn, updating # a BrowserPlugin property will update the corresponding BrowserPlugin # attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more # details. handleWebviewAttributeMutation: (attributeName, oldValue, newValue) -> - # Certain changes (such as internally-initiated changes) to attributes should - # not be handled normally. - if @attributes[attributeName]?.ignoreNextMutation - @attributes[attributeName].ignoreNextMutation = false + if not @attributes[attributeName] or @attributes[attributeName].ignoreMutation return - if attributeName in AUTO_SIZE_ATTRIBUTES - return unless @guestInstanceId - guestViewInternal.setAutoSize @guestInstanceId, - enableAutoSize: @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(), - min: - width: parseInt @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0 - height: parseInt @attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0 - max: - width: parseInt @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0 - height: parseInt @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0 - else if attributeName is webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY - # We treat null attribute (attribute removed) and the empty string as - # one case. - oldValue ?= '' - newValue ?= '' - - return if oldValue is newValue and not @guestInstanceId - - guestViewInternal.setAllowTransparency @guestInstanceId, @attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY].getValue() - else if attributeName is webViewConstants.ATTRIBUTE_HTTPREFERRER - oldValue ?= '' - newValue ?= '' - - if newValue == '' and oldValue != '' - @webviewNode.setAttribute webViewConstants.ATTRIBUTE_HTTPREFERRER, oldValue - - @attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].setValue newValue - - # If the httpreferrer changes treat it as though the src changes and reload - # the page with the new httpreferrer. - @parseSrcAttribute() - else if attributeName is webViewConstants.ATTRIBUTE_SRC - # We treat null attribute (attribute removed) and the empty string as - # one case. - oldValue ?= '' - newValue ?= '' - # Once we have navigated, we don't allow clearing the src attribute. - # Once enters a navigated state, it cannot return to a - # placeholder state. - if newValue == '' and oldValue != '' - # src attribute changes normally initiate a navigation. We suppress - # the next src attribute handler call to avoid reloading the page - # on every guest-initiated navigation. - @ignoreNextSrcAttributeChange = true - @webviewNode.setAttribute webViewConstants.ATTRIBUTE_SRC, oldValue - return - - if @ignoreNextSrcAttributeChange - # Don't allow the src mutation observer to see this change. - @srcAndPartitionObserver.takeRecords() - @ignoreNextSrcAttributeChange = false - return - @parseSrcAttribute() - else if attributeName is webViewConstants.ATTRIBUTE_PARTITION - @attributes[webViewConstants.ATTRIBUTE_PARTITION].handleMutation oldValue, newValue + # Let the changed attribute handle its own mutation; + @attributes[attributeName].handleMutation oldValue, newValue handleBrowserPluginAttributeMutation: (attributeName, oldValue, newValue) -> if attributeName is webViewConstants.ATTRIBUTE_INTERNALINSTANCEID and !oldValue and !!newValue @@ -330,25 +239,19 @@ class WebView if isTopLevel and (oldValue != newValue) # Touching the src attribute triggers a navigation. To avoid # triggering a page reload on every guest-initiated navigation, - # we use the flag ignoreNextSrcAttributeChange here. - this.ignoreNextSrcAttributeChange = true - this.webviewNode.setAttribute webViewConstants.ATTRIBUTE_SRC, newValue + # we do not handle this mutation + @attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation newValue onAttach: (storagePartitionId) -> @attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue storagePartitionId buildAttachParams: -> - instanceId: @viewInstanceId - userAgentOverride: @userAgentOverride - # Attributes: - allowtransparency: @attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY].getValue() - autosize: @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() - maxheight: parseInt @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0 - maxwidth: parseInt @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0 - minheight: parseInt @attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0 - minwidth: parseInt @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0 - src: @attributes[webViewConstants.ATTRIBUTE_SRC].getValue() - httpreferrer: @attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() + params = + instanceId: @viewInstanceId + userAgentOverride: @userAgentOverride + for attributeName, attribute of @attributes + params[attributeName] = attribute.getValue() + params attachWindow: (guestInstanceId) -> @guestInstanceId = guestInstanceId