web-view-attributes.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { WEB_VIEW_ATTRIBUTES, WEB_VIEW_ERROR_MESSAGES } from '@electron/internal/renderer/web-view/web-view-constants';
  2. import type { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl';
  3. const resolveURL = function (url?: string | null) {
  4. return url ? new URL(url, location.href).href : '';
  5. };
  6. interface MutationHandler {
  7. handleMutation (_oldValue: any, _newValue: any): any;
  8. }
  9. // Attribute objects.
  10. // Default implementation of a WebView attribute.
  11. export class WebViewAttribute implements MutationHandler {
  12. public value: any;
  13. public ignoreMutation = false;
  14. constructor (public name: string, public webViewImpl: WebViewImpl) {
  15. this.name = name;
  16. this.value = (webViewImpl.webviewNode as Record<string, any>)[name] || '';
  17. this.webViewImpl = webViewImpl;
  18. this.defineProperty();
  19. }
  20. // Retrieves and returns the attribute's value.
  21. public getValue () {
  22. return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value;
  23. }
  24. // Sets the attribute's value.
  25. public setValue (value: any) {
  26. this.webViewImpl.webviewNode.setAttribute(this.name, value || '');
  27. }
  28. // Changes the attribute's value without triggering its mutation handler.
  29. public setValueIgnoreMutation (value: any) {
  30. this.ignoreMutation = true;
  31. this.setValue(value);
  32. this.ignoreMutation = false;
  33. }
  34. // Defines this attribute as a property on the webview node.
  35. public defineProperty () {
  36. return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
  37. get: () => {
  38. return this.getValue();
  39. },
  40. set: (value) => {
  41. return this.setValue(value);
  42. },
  43. enumerable: true
  44. });
  45. }
  46. // Called when the attribute's value changes.
  47. public handleMutation: MutationHandler['handleMutation'] = () => undefined;
  48. }
  49. // An attribute that is treated as a Boolean.
  50. class BooleanAttribute extends WebViewAttribute {
  51. getValue () {
  52. return this.webViewImpl.webviewNode.hasAttribute(this.name);
  53. }
  54. setValue (value: boolean) {
  55. if (value) {
  56. this.webViewImpl.webviewNode.setAttribute(this.name, '');
  57. } else {
  58. this.webViewImpl.webviewNode.removeAttribute(this.name);
  59. }
  60. }
  61. }
  62. // Attribute representing the state of the storage partition.
  63. export class PartitionAttribute extends WebViewAttribute {
  64. public validPartitionId = true;
  65. constructor (public webViewImpl: WebViewImpl) {
  66. super(WEB_VIEW_ATTRIBUTES.PARTITION, webViewImpl);
  67. }
  68. public handleMutation = (oldValue: any, newValue: any) => {
  69. newValue = newValue || '';
  70. // The partition cannot change if the webview has already navigated.
  71. if (!this.webViewImpl.beforeFirstNavigation) {
  72. console.error(WEB_VIEW_ERROR_MESSAGES.ALREADY_NAVIGATED);
  73. this.setValueIgnoreMutation(oldValue);
  74. return;
  75. }
  76. if (newValue === 'persist:') {
  77. this.validPartitionId = false;
  78. console.error(WEB_VIEW_ERROR_MESSAGES.INVALID_PARTITION_ATTRIBUTE);
  79. }
  80. };
  81. }
  82. // Attribute that handles the location and navigation of the webview.
  83. export class SrcAttribute extends WebViewAttribute {
  84. public observer!: MutationObserver;
  85. constructor (public webViewImpl: WebViewImpl) {
  86. super(WEB_VIEW_ATTRIBUTES.SRC, webViewImpl);
  87. this.setupMutationObserver();
  88. }
  89. public getValue () {
  90. if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  91. return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
  92. } else {
  93. return this.value;
  94. }
  95. }
  96. public setValueIgnoreMutation (value: any) {
  97. super.setValueIgnoreMutation(value);
  98. // takeRecords() is needed to clear queued up src mutations. Without it, it
  99. // is possible for this change to get picked up asynchronously by src's
  100. // mutation observer |observer|, and then get handled even though we do not
  101. // want to handle this mutation.
  102. this.observer.takeRecords();
  103. }
  104. public handleMutation = (oldValue: any, newValue: any) => {
  105. // Once we have navigated, we don't allow clearing the src attribute.
  106. // Once <webview> enters a navigated state, it cannot return to a
  107. // placeholder state.
  108. if (!newValue && oldValue) {
  109. // src attribute changes normally initiate a navigation. We suppress
  110. // the next src attribute handler call to avoid reloading the page
  111. // on every guest-initiated navigation.
  112. this.setValueIgnoreMutation(oldValue);
  113. return;
  114. }
  115. this.parse();
  116. };
  117. // The purpose of this mutation observer is to catch assignment to the src
  118. // attribute without any changes to its value. This is useful in the case
  119. // where the webview guest has crashed and navigating to the same address
  120. // spawns off a new process.
  121. public setupMutationObserver () {
  122. this.observer = new MutationObserver((mutations) => {
  123. for (const mutation of mutations) {
  124. const { oldValue } = mutation;
  125. const newValue = this.getValue();
  126. if (oldValue !== newValue) {
  127. return;
  128. }
  129. this.handleMutation(oldValue, newValue);
  130. }
  131. });
  132. const params = {
  133. attributes: true,
  134. attributeOldValue: true,
  135. attributeFilter: [this.name]
  136. };
  137. this.observer.observe(this.webViewImpl.webviewNode, params);
  138. }
  139. public parse () {
  140. if (!this.webViewImpl.elementAttached || !(this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION) as PartitionAttribute).validPartitionId || !this.getValue()) {
  141. return;
  142. }
  143. if (this.webViewImpl.guestInstanceId == null) {
  144. if (this.webViewImpl.beforeFirstNavigation) {
  145. this.webViewImpl.beforeFirstNavigation = false;
  146. this.webViewImpl.createGuest();
  147. }
  148. return;
  149. }
  150. // Navigate to |this.src|.
  151. const opts: Record<string, string> = {};
  152. const httpreferrer = this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.HTTPREFERRER)!.getValue();
  153. if (httpreferrer) {
  154. opts.httpReferrer = httpreferrer;
  155. }
  156. const useragent = this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.USERAGENT)!.getValue();
  157. if (useragent) {
  158. opts.userAgent = useragent;
  159. }
  160. (this.webViewImpl.webviewNode as Electron.WebviewTag).loadURL(this.getValue(), opts)
  161. .catch(err => {
  162. console.error('Unexpected error while loading URL', err);
  163. });
  164. }
  165. }
  166. // Attribute specifies HTTP referrer.
  167. class HttpReferrerAttribute extends WebViewAttribute {
  168. constructor (webViewImpl: WebViewImpl) {
  169. super(WEB_VIEW_ATTRIBUTES.HTTPREFERRER, webViewImpl);
  170. }
  171. }
  172. // Attribute specifies user agent
  173. class UserAgentAttribute extends WebViewAttribute {
  174. constructor (webViewImpl: WebViewImpl) {
  175. super(WEB_VIEW_ATTRIBUTES.USERAGENT, webViewImpl);
  176. }
  177. }
  178. // Attribute that set preload script.
  179. class PreloadAttribute extends WebViewAttribute {
  180. constructor (webViewImpl: WebViewImpl) {
  181. super(WEB_VIEW_ATTRIBUTES.PRELOAD, webViewImpl);
  182. }
  183. public getValue () {
  184. if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  185. return this.value;
  186. }
  187. let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
  188. const protocol = preload.substr(0, 5);
  189. if (protocol !== 'file:') {
  190. console.error(WEB_VIEW_ERROR_MESSAGES.INVALID_PRELOAD_ATTRIBUTE);
  191. preload = '';
  192. }
  193. return preload;
  194. }
  195. }
  196. // Attribute that specifies the blink features to be enabled.
  197. class BlinkFeaturesAttribute extends WebViewAttribute {
  198. constructor (webViewImpl: WebViewImpl) {
  199. super(WEB_VIEW_ATTRIBUTES.BLINKFEATURES, webViewImpl);
  200. }
  201. }
  202. // Attribute that specifies the blink features to be disabled.
  203. class DisableBlinkFeaturesAttribute extends WebViewAttribute {
  204. constructor (webViewImpl: WebViewImpl) {
  205. super(WEB_VIEW_ATTRIBUTES.DISABLEBLINKFEATURES, webViewImpl);
  206. }
  207. }
  208. // Attribute that specifies the web preferences to be enabled.
  209. class WebPreferencesAttribute extends WebViewAttribute {
  210. constructor (webViewImpl: WebViewImpl) {
  211. super(WEB_VIEW_ATTRIBUTES.WEBPREFERENCES, webViewImpl);
  212. }
  213. }
  214. // Sets up all of the webview attributes.
  215. export function setupWebViewAttributes (self: WebViewImpl) {
  216. return new Map<string, WebViewAttribute>([
  217. [WEB_VIEW_ATTRIBUTES.PARTITION, new PartitionAttribute(self)],
  218. [WEB_VIEW_ATTRIBUTES.SRC, new SrcAttribute(self)],
  219. [WEB_VIEW_ATTRIBUTES.HTTPREFERRER, new HttpReferrerAttribute(self)],
  220. [WEB_VIEW_ATTRIBUTES.USERAGENT, new UserAgentAttribute(self)],
  221. [WEB_VIEW_ATTRIBUTES.NODEINTEGRATION, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATION, self)],
  222. [WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, self)],
  223. [WEB_VIEW_ATTRIBUTES.PLUGINS, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.PLUGINS, self)],
  224. [WEB_VIEW_ATTRIBUTES.DISABLEWEBSECURITY, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.DISABLEWEBSECURITY, self)],
  225. [WEB_VIEW_ATTRIBUTES.ALLOWPOPUPS, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.ALLOWPOPUPS, self)],
  226. [WEB_VIEW_ATTRIBUTES.PRELOAD, new PreloadAttribute(self)],
  227. [WEB_VIEW_ATTRIBUTES.BLINKFEATURES, new BlinkFeaturesAttribute(self)],
  228. [WEB_VIEW_ATTRIBUTES.DISABLEBLINKFEATURES, new DisableBlinkFeaturesAttribute(self)],
  229. [WEB_VIEW_ATTRIBUTES.WEBPREFERENCES, new WebPreferencesAttribute(self)]
  230. ]);
  231. }