options.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. var defaults = {
  2. // storage
  3. origins: {},
  4. header: false,
  5. csp: false,
  6. // static
  7. protocols: ['https', 'http', '*'],
  8. // UI
  9. protocol: 'https',
  10. origin: '',
  11. timeout: null,
  12. file: true,
  13. }
  14. var state = Object.assign({}, defaults)
  15. var events = {
  16. file: () => {
  17. chrome.tabs.create({url: 'chrome://extensions/?id=' + chrome.runtime.id})
  18. },
  19. header: (e) => {
  20. state.header = !state.header
  21. chrome.runtime.sendMessage({
  22. message: 'options.header',
  23. header: state.header,
  24. })
  25. },
  26. csp: (e) => {
  27. ;((done) => {
  28. // ff: webRequest is required permission
  29. if (/Firefox/.test(navigator.userAgent)) {
  30. done()
  31. }
  32. else {
  33. var action = state.csp ? 'remove' : 'request'
  34. chrome.permissions[action]({
  35. permissions: ['webRequest', 'webRequestBlocking']
  36. }, done)
  37. }
  38. })(() => {
  39. state.csp = !state.csp
  40. chrome.runtime.sendMessage({
  41. message: 'options.csp',
  42. csp: state.csp,
  43. })
  44. m.redraw()
  45. })
  46. },
  47. origin: {
  48. protocol: (e) => {
  49. state.protocol = state.protocols[e.target.selectedIndex]
  50. },
  51. name: (e) => {
  52. state.origin = e.target.value
  53. },
  54. add: () => {
  55. var domain = state.origin.replace(/.*:\/\/([^/]+).*/i, '$1')
  56. if (!domain) {
  57. return
  58. }
  59. var origin = state.protocol + '://' + domain
  60. chrome.permissions.request({origins: [origin + '/*']}, (granted) => {
  61. if (granted) {
  62. chrome.runtime.sendMessage({message: 'origin.add', origin}, init)
  63. }
  64. })
  65. },
  66. all: () => {
  67. var origin = '*://*'
  68. chrome.permissions.request({origins: [origin + '/*']}, (granted) => {
  69. if (granted) {
  70. chrome.runtime.sendMessage({message: 'origin.add', origin}, init)
  71. }
  72. })
  73. },
  74. remove: (origin) => () => {
  75. chrome.permissions.remove({origins: [origin + '/*']}, (removed) => {
  76. if (removed) {
  77. chrome.runtime.sendMessage({message: 'origin.remove', origin}, init)
  78. }
  79. })
  80. },
  81. update: (origin) => (e) => {
  82. state.origins[origin] = e.target.value
  83. clearTimeout(state.timeout)
  84. state.timeout = setTimeout(() => {
  85. chrome.runtime.sendMessage({
  86. message: 'origin.update', origin, match: e.target.value
  87. })
  88. }, 750)
  89. },
  90. refresh: (origin) => () => {
  91. chrome.permissions.request({origins: [origin + '/*']})
  92. },
  93. },
  94. }
  95. chrome.extension.isAllowedFileSchemeAccess((isAllowedAccess) => {
  96. state.file = /Firefox/.test(navigator.userAgent)
  97. ? true // ff: `Allow access to file URLs` option isn't available
  98. : isAllowedAccess
  99. m.redraw()
  100. })
  101. var init = () => {
  102. chrome.runtime.sendMessage({message: 'options'}, (res) => {
  103. state = Object.assign({}, defaults, {file: state.file}, res)
  104. m.redraw()
  105. })
  106. }
  107. init()
  108. var oncreate = {
  109. ripple: (vnode) => {
  110. mdc.ripple.MDCRipple.attachTo(vnode.dom)
  111. }
  112. }
  113. var onupdate = {
  114. header: (vnode) => {
  115. if (vnode.dom.classList.contains('is-checked') !== state.header) {
  116. vnode.dom.classList.toggle('is-checked')
  117. }
  118. },
  119. csp: (vnode) => {
  120. if (vnode.dom.classList.contains('is-checked') !== state.csp) {
  121. vnode.dom.classList.toggle('is-checked')
  122. }
  123. }
  124. }
  125. m.mount(document.querySelector('main'), {
  126. view: () =>
  127. m('#options',
  128. // file access is disabled
  129. (!state.file || null) &&
  130. m('.bs-callout m-file',
  131. m('h4.mdc-typography--headline', 'Access to file:// URLs is Disabled'),
  132. m('img.mdc-elevation--z2', {src: '/images/file-urls.png'}),
  133. m('button.mdc-button mdc-button--raised m-button', {
  134. oncreate: oncreate.ripple,
  135. onclick: events.file
  136. },
  137. 'Enable Access to file:// URLs'
  138. )
  139. ),
  140. // allowed origins
  141. m('.bs-callout m-origins',
  142. m('h4.mdc-typography--headline', 'Allowed Origins'),
  143. // add origin
  144. m('select.mdc-elevation--z2 m-select', {
  145. onchange: events.origin.protocol
  146. },
  147. state.protocols.map((protocol) =>
  148. m('option', {
  149. value: protocol,
  150. selected: protocol === state.protocol
  151. },
  152. protocol + '://'
  153. )
  154. )),
  155. m('.mdc-textfield m-textfield',
  156. m('input.mdc-textfield__input', {
  157. type: 'text',
  158. value: state.origin,
  159. onchange: events.origin.name,
  160. placeholder: 'raw.githubusercontent.com'
  161. })
  162. ),
  163. m('button.mdc-button mdc-button--raised m-button', {
  164. oncreate: oncreate.ripple,
  165. onclick: events.origin.add
  166. },
  167. 'Add'
  168. ),
  169. m('button.mdc-button mdc-button--raised m-button', {
  170. oncreate: oncreate.ripple,
  171. onclick: events.origin.all
  172. },
  173. 'Allow All'
  174. ),
  175. // header detection - ff: disabled
  176. (!/Firefox/.test(navigator.userAgent) || null) &&
  177. m('label.mdc-switch m-switch', {
  178. onupdate: onupdate.header,
  179. title: 'Toggle header detection'
  180. },
  181. m('input.mdc-switch__native-control', {
  182. type: 'checkbox',
  183. checked: state.header,
  184. onchange: events.header
  185. }),
  186. m('.mdc-switch__background', m('.mdc-switch__knob')),
  187. m('span.mdc-switch-label',
  188. 'Detect ',
  189. m('code', 'text/markdown'),
  190. ' and ',
  191. m('code', 'text/x-markdown'),
  192. ' content type'
  193. )
  194. ),
  195. // csp
  196. m('label.mdc-switch m-switch', {
  197. onupdate: onupdate.csp,
  198. title: 'Disable Content Security Policy (CSP)'
  199. },
  200. m('input.mdc-switch__native-control', {
  201. type: 'checkbox',
  202. checked: state.csp,
  203. onchange: events.csp
  204. }),
  205. m('.mdc-switch__background', m('.mdc-switch__knob')),
  206. m('span.mdc-switch-label',
  207. 'Disable ',
  208. m('code', 'Content Security Policy'),
  209. )
  210. ),
  211. m('ul.mdc-elevation--z2 m-list',
  212. Object.keys(state.origins).sort().map((origin) =>
  213. (
  214. (
  215. state.file && origin === 'file://' &&
  216. // ff: access to file:// URLs is not allowed
  217. !/Firefox/.test(navigator.userAgent)
  218. )
  219. || origin !== 'file://' || null
  220. ) &&
  221. m('li',
  222. m('span', origin.replace(/^(\*|file|http(s)?).*/, '$1')),
  223. m('span', origin.replace(/^(\*|file|http(s)?):\/\//, '')),
  224. m('.mdc-textfield m-textfield', {
  225. oncreate: oncreate.textfield
  226. },
  227. m('input.mdc-textfield__input', {
  228. type: 'text',
  229. onkeyup: events.origin.update(origin),
  230. value: state.origins[origin],
  231. })
  232. ),
  233. (origin !== 'file://' || null) &&
  234. m('span',
  235. m('button.mdc-button', {
  236. oncreate: oncreate.ripple,
  237. onclick: events.origin.refresh(origin),
  238. title: 'Refresh'
  239. },
  240. m('i.material-icons icon-refresh')
  241. ),
  242. m('button.mdc-button', {
  243. oncreate: oncreate.ripple,
  244. onclick: events.origin.remove(origin),
  245. title: 'Remove'
  246. },
  247. m('i.material-icons icon-remove')
  248. )
  249. )
  250. )
  251. )
  252. )
  253. ),
  254. )
  255. })