web-view-attributes.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. 'use strict'
  2. const WebViewImpl = require('./web-view')
  3. const guestViewInternal = require('./guest-view-internal')
  4. const webViewConstants = require('./web-view-constants')
  5. const {remote} = require('electron')
  6. // Helper function to resolve url set in attribute.
  7. const a = document.createElement('a')
  8. const resolveURL = function (url) {
  9. if (url === '') return ''
  10. a.href = url
  11. return a.href
  12. }
  13. // Attribute objects.
  14. // Default implementation of a WebView attribute.
  15. class WebViewAttribute {
  16. constructor (name, webViewImpl) {
  17. this.name = name
  18. this.value = webViewImpl.webviewNode[name] || ''
  19. this.webViewImpl = webViewImpl
  20. this.ignoreMutation = false
  21. this.defineProperty()
  22. }
  23. // Retrieves and returns the attribute's value.
  24. getValue () {
  25. return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value
  26. }
  27. // Sets the attribute's value.
  28. setValue (value) {
  29. this.webViewImpl.webviewNode.setAttribute(this.name, value || '')
  30. }
  31. // Changes the attribute's value without triggering its mutation handler.
  32. setValueIgnoreMutation (value) {
  33. this.ignoreMutation = true
  34. this.setValue(value)
  35. this.ignoreMutation = false
  36. }
  37. // Defines this attribute as a property on the webview node.
  38. defineProperty () {
  39. return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
  40. get: () => {
  41. return this.getValue()
  42. },
  43. set: (value) => {
  44. return this.setValue(value)
  45. },
  46. enumerable: true
  47. })
  48. }
  49. // Called when the attribute's value changes.
  50. handleMutation () {}
  51. }
  52. // An attribute that is treated as a Boolean.
  53. class BooleanAttribute extends WebViewAttribute {
  54. getValue () {
  55. return this.webViewImpl.webviewNode.hasAttribute(this.name)
  56. }
  57. setValue (value) {
  58. if (value) {
  59. this.webViewImpl.webviewNode.setAttribute(this.name, '')
  60. } else {
  61. this.webViewImpl.webviewNode.removeAttribute(this.name)
  62. }
  63. }
  64. }
  65. // Attribute used to define the demension limits of autosizing.
  66. class AutosizeDimensionAttribute extends WebViewAttribute {
  67. getValue () {
  68. return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0
  69. }
  70. handleMutation () {
  71. if (!this.webViewImpl.guestInstanceId) {
  72. return
  73. }
  74. guestViewInternal.setSize(this.webViewImpl.guestInstanceId, {
  75. enableAutoSize: this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(),
  76. min: {
  77. width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0),
  78. height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0)
  79. },
  80. max: {
  81. width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0),
  82. height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0)
  83. }
  84. })
  85. }
  86. }
  87. // Attribute that specifies whether the webview should be autosized.
  88. class AutosizeAttribute extends BooleanAttribute {
  89. constructor (webViewImpl) {
  90. super(webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl)
  91. }
  92. }
  93. AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation
  94. // Attribute representing the state of the storage partition.
  95. class PartitionAttribute extends WebViewAttribute {
  96. constructor (webViewImpl) {
  97. super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl)
  98. this.validPartitionId = true
  99. }
  100. handleMutation (oldValue, newValue) {
  101. newValue = newValue || ''
  102. // The partition cannot change if the webview has already navigated.
  103. if (!this.webViewImpl.beforeFirstNavigation) {
  104. window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED)
  105. this.setValueIgnoreMutation(oldValue)
  106. return
  107. }
  108. if (newValue === 'persist:') {
  109. this.validPartitionId = false
  110. window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE)
  111. }
  112. }
  113. }
  114. // An attribute that controls the guest instance this webview is connected to
  115. class GuestInstanceAttribute extends WebViewAttribute {
  116. constructor (webViewImpl) {
  117. super(webViewConstants.ATTRIBUTE_GUESTINSTANCE, webViewImpl)
  118. }
  119. // Retrieves and returns the attribute's value.
  120. getValue () {
  121. if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  122. return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name))
  123. }
  124. }
  125. // Sets the attribute's value.
  126. setValue (value) {
  127. if (!value) {
  128. this.webViewImpl.webviewNode.removeAttribute(this.name)
  129. } else if (!isNaN(value)) {
  130. this.webViewImpl.webviewNode.setAttribute(this.name, value)
  131. }
  132. }
  133. handleMutation (oldValue, newValue) {
  134. if (!newValue) {
  135. this.webViewImpl.reset()
  136. return
  137. }
  138. const intVal = parseInt(newValue)
  139. if (!isNaN(newValue) && remote.getGuestWebContents(intVal)) {
  140. this.webViewImpl.attachGuestInstance(intVal)
  141. } else {
  142. this.setValueIgnoreMutation(oldValue)
  143. }
  144. }
  145. }
  146. // Attribute that handles the location and navigation of the webview.
  147. class SrcAttribute extends WebViewAttribute {
  148. constructor (webViewImpl) {
  149. super(webViewConstants.ATTRIBUTE_SRC, webViewImpl)
  150. this.setupMutationObserver()
  151. }
  152. getValue () {
  153. if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  154. return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
  155. } else {
  156. return this.value
  157. }
  158. }
  159. setValueIgnoreMutation (value) {
  160. super.setValueIgnoreMutation(value)
  161. // takeRecords() is needed to clear queued up src mutations. Without it, it
  162. // is possible for this change to get picked up asyncronously by src's
  163. // mutation observer |observer|, and then get handled even though we do not
  164. // want to handle this mutation.
  165. this.observer.takeRecords()
  166. }
  167. handleMutation (oldValue, newValue) {
  168. // Once we have navigated, we don't allow clearing the src attribute.
  169. // Once <webview> enters a navigated state, it cannot return to a
  170. // placeholder state.
  171. if (!newValue && oldValue) {
  172. // src attribute changes normally initiate a navigation. We suppress
  173. // the next src attribute handler call to avoid reloading the page
  174. // on every guest-initiated navigation.
  175. this.setValueIgnoreMutation(oldValue)
  176. return
  177. }
  178. this.parse()
  179. }
  180. // The purpose of this mutation observer is to catch assignment to the src
  181. // attribute without any changes to its value. This is useful in the case
  182. // where the webview guest has crashed and navigating to the same address
  183. // spawns off a new process.
  184. setupMutationObserver () {
  185. this.observer = new MutationObserver((mutations) => {
  186. for (const mutation of mutations) {
  187. const {oldValue} = mutation
  188. const newValue = this.getValue()
  189. if (oldValue !== newValue) {
  190. return
  191. }
  192. this.handleMutation(oldValue, newValue)
  193. }
  194. })
  195. const params = {
  196. attributes: true,
  197. attributeOldValue: true,
  198. attributeFilter: [this.name]
  199. }
  200. this.observer.observe(this.webViewImpl.webviewNode, params)
  201. }
  202. parse () {
  203. if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId) {
  204. return
  205. }
  206. if (this.webViewImpl.guestInstanceId == null) {
  207. if (this.webViewImpl.beforeFirstNavigation) {
  208. this.webViewImpl.beforeFirstNavigation = false
  209. this.webViewImpl.createGuest()
  210. }
  211. return
  212. }
  213. if (!this.getValue()) {
  214. return
  215. }
  216. // Navigate to |this.src|.
  217. const opts = {}
  218. const httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue()
  219. if (httpreferrer) {
  220. opts.httpReferrer = httpreferrer
  221. }
  222. const useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue()
  223. if (useragent) {
  224. opts.userAgent = useragent
  225. }
  226. const guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId)
  227. guestContents.loadURL(this.getValue(), opts)
  228. }
  229. }
  230. // Attribute specifies HTTP referrer.
  231. class HttpReferrerAttribute extends WebViewAttribute {
  232. constructor (webViewImpl) {
  233. super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl)
  234. }
  235. }
  236. // Attribute specifies user agent
  237. class UserAgentAttribute extends WebViewAttribute {
  238. constructor (webViewImpl) {
  239. super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl)
  240. }
  241. }
  242. // Attribute that set preload script.
  243. class PreloadAttribute extends WebViewAttribute {
  244. constructor (webViewImpl) {
  245. super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl)
  246. }
  247. getValue () {
  248. if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  249. return this.value
  250. }
  251. let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
  252. const protocol = preload.substr(0, 5)
  253. if (protocol !== 'file:') {
  254. console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE)
  255. preload = ''
  256. }
  257. return preload
  258. }
  259. }
  260. // Attribute that specifies the blink features to be enabled.
  261. class BlinkFeaturesAttribute extends WebViewAttribute {
  262. constructor (webViewImpl) {
  263. super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl)
  264. }
  265. }
  266. // Attribute that specifies the blink features to be disabled.
  267. class DisableBlinkFeaturesAttribute extends WebViewAttribute {
  268. constructor (webViewImpl) {
  269. super(webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl)
  270. }
  271. }
  272. // Attribute that specifies the web preferences to be enabled.
  273. class WebPreferencesAttribute extends WebViewAttribute {
  274. constructor (webViewImpl) {
  275. super(webViewConstants.ATTRIBUTE_WEBPREFERENCES, webViewImpl)
  276. }
  277. }
  278. // Sets up all of the webview attributes.
  279. WebViewImpl.prototype.setupWebViewAttributes = function () {
  280. this.attributes = {}
  281. this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this)
  282. this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this)
  283. this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this)
  284. this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this)
  285. this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this)
  286. this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this)
  287. this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this)
  288. this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this)
  289. this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this)
  290. this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
  291. this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
  292. this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
  293. this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE] = new GuestInstanceAttribute(this)
  294. this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE, this)
  295. this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this)
  296. const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]
  297. autosizeAttributes.forEach((attribute) => {
  298. this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this)
  299. })
  300. }