Beyond The Tab: Executing JavaScript Across Browser Contexts @ Full Stack Fest

Beyond The Tab: Executing JavaScript Across Browser Contexts @ Full Stack Fest

Keeping JavaScript from interfering across tabs is great, but what about when a web application wants to share state without a server? You'll leave this talk with enough knowledge to get started with SharedWorkers, ServiceWorkers, and other techniques - and enough wisdom to know when to use them.

2e055eb589fb86174fd268748b0fcd30?s=128

Andrew Dunkman

September 09, 2016
Tweet

Transcript

  1. @adunkman Executing JavaScript Across Browser Contexts

  2. GraphQL

  3. None
  4. None
  5. @adunkman Standard Browser APIs

  6. @adunkman Between Tabs Outside Tabs

  7. None
  8. Washington, DC

  9. Pittsburgh, PA

  10. Los Angeles, CA

  11. New Paltz, NY

  12. Mayne Island, BC

  13. None
  14. None
  15. None
  16. @adunkman You need to write well to program

  17. @adunkman You don’t need to hear to program

  18. @adunkman Access to learning resources

  19. None
  20. None
  21. None
  22. None
  23. None
  24. @adunkman confa11y.com

  25. @adunkman confally.com

  26. @adunkman a11yconf.com

  27. @adunkman allyconf.com

  28. None
  29. None
  30. @adunkman Pull Request Opportunities

  31. None
  32. @adunkman Standard Browser APIs

  33. None
  34. None
  35. None
  36. None
  37. None
  38. None
  39. None
  40. None
  41. None
  42. None
  43. None
  44. None
  45. None
  46. @adunkman

  47. @adunkman

  48. @adunkman Cache-Control

  49. @adunkman

  50. @adunkman There’s more to the web than web pages

  51. @adunkman Browser Contexts

  52. @adunkman Window Sandbox

  53. @adunkman Domain-Level Objects

  54. None
  55. None
  56. @adunkman Why isn’t this better?

  57. @adunkman document.cookie

  58. @adunkman –Jen Kramer “It’s back again baby! Everything old is

    new again!”
  59. Bob Dunkman c. 1977

  60. Bob Dunkman c. 2016

  61. @adunkman document.cookie

  62. In document.cookie = cookieName + "=" + encodeURIComponent(json);

  63. In Out document.cookie = cookieName + "=" + encodeURIComponent(json); var

    regex = new RegExp( "(?:(?:^|.*;)\\s*" + n + "\\s*\\=\\s*([^;]*).*$)|^.*$");
  64. In Out Detect document.cookie = cookieName + "=" + encodeURIComponent(json);

    var regex = new RegExp( "(?:(?:^|.*;)\\s*" + n + "\\s*\\=\\s*([^;]*).*$)|^.*$"); setInterval(checkForChanges, pollingInterval);
  65. @adunkman Please don’t.

  66. @adunkman document.cookie localStorage

  67. @adunkman QuotaExceededError

  68. @adunkman document.cookie localStorage

  69. In localStorage.setItem( keyName, json);

  70. In Out localStorage.setItem( keyName, json); localStorage.getItem( keyName);

  71. In Out Detect localStorage.setItem( keyName, json); localStorage.getItem( keyName); window.addEventListener( “storage",

    function (event) { ...
  72. None
  73. var tabId = Math.random(); video.addEventListener("play", function () { localStorage.setItem("playing_tab", tabId);

    }); window.addEventListener("storage", function (event) { var isThisKey = event.key === "playing_tab"; var notThisTab = event.newValue !== tabId; if (isThisKey && notThisTab) { video.pause(); } });
  74. var tabId = Math.random(); video.addEventListener("play", function () { localStorage.setItem("playing_tab", tabId);

    }); window.addEventListener("storage", function (event) { if (event.key === "playing_tab" && event.newValue !== tabId) { video.pause(); } });
  75. var tabId = Math.random(); video.addEventListener("play", function () { localStorage.setItem("playing_tab", tabId);

    }); window.addEventListener("storage", function (event) { if (event.key === "playing_tab" && event.newValue !== tabId) { video.pause(); } }); 9
  76. None
  77. @adunkman Between Tabs Outside Tabs

  78. @adunkman WebWorker

  79. @adunkman SharedWorker

  80. @adunkman Tab 1 Tab 2 Tab 3 worker.js 1 worker.js

    2 worker.js 3
  81. @adunkman Tab 1 Tab 2 Tab 3 worker.js 1

  82. None
  83. None
  84. None
  85. None
  86. $

  87. None
  88. External Page

  89. External Page Harvest Script

  90. External Page Harvest Script

  91. External Page Harvest Script <iframe>

  92. External Page Harvest Script <iframe> SharedWorker

  93. External Page Harvest Script <iframe> SharedWorker WebSocket

  94. None
  95. None
  96. None
  97. None
  98. None
  99. None
  100. None
  101. @adunkman SharedWorker

  102. @adunkman Great for sharing processing and resources Not great for

    
 working in Safari
  103. @adunkman SharedWorker

  104. @adunkman ServiceWorker

  105. @adunkman Service workers essentially act as proxy servers that sit

    between web applications, and the browser and network (when available). … They will also allow access to push notifications and background sync APIs. –MDN
  106. @adunkman Service workers essentially act as proxy servers that sit

    between web applications, and the browser and network (when available). … They will also allow access to push notifications and background sync APIs. –MDN
  107. @adunkman A service worker is an event- driven worker registered

    against an origin and a path. –MDN
  108. @adunkman A service worker is an event- driven worker registered

    against an origin and a path. –MDN
  109. @adunkman Service workers are a new browser feature that provide

    event-driven scripts that run independently of web pages. –W3C
  110. @adunkman Service workers are a new browser feature that provide

    event-driven scripts that run independently of web pages. –W3C
  111. @adunkman A service worker is a process which is tied

    to domain events rather than to a browsing context. –me
  112. @adunkman Tab 1 worker.js 1 Domain Events worker.js 1

  113. @adunkman Domain Events worker.js 1 push sync fetch

  114. @adunkman “push” event

  115. None
  116. None
  117. None
  118. None
  119. None
  120. None
  121. None
  122. None
  123. @adunkman Notification Service

  124. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); });
  125. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); }); Tab 1 worker.js 1
  126. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); }); Tab 1 worker.js 1 (new version)
  127. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); }); Tab 1 worker.js 1 Tab 2 (new version)
  128. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); }); Tab 1 worker.js 1 Tab 2 (old) worker.js 1 (new version)
  129. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); });
  130. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); }); Tab 2 (new) worker.js 1
  131. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); }); Worker self.addEventListener("install", function (event) { self.skipWaiting(); });
  132. Worker Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err)

    { console.log("fail", err); }); self.addEventListener("push", function (event) { console.log("message", event); }); self.addEventListener("install", function (event) { self.skipWaiting(); });
  133. None
  134. @adunkman “push” event

  135. @adunkman Browser message server (like native app) Wakes ServiceWorker to

    process event
  136. None
  137. @adunkman “sync” event

  138. None
  139. None
  140. None
  141. None
  142. None
  143. None
  144. Worker Page navigator.serviceWorker.ready .then(function (reg) { return reg.sync.register("syncTime"); }); self.addEventListener("sync",

    function (event) { if (event.tag === "syncTime") { event.waitUntil(stuff()); } });
  145. Tab 1 worker.js 1

  146. Tab 1 worker.js 1 sync “syncTime”

  147. Tab 1 worker.js 1 sync “syncTime” Browser

  148. worker.js 1 Browser

  149. worker.js 1 Browser

  150. @adunkman “sync” event

  151. @adunkman Great for backgrounding a sync process, even in limited

    or no connectivity
  152. None
  153. @adunkman “fetch” event

  154. None
  155. None
  156. Worker self.addEventListener("fetch", function (event) { console.log(event.request.url); event.respondWith( caches.match(event.request) .then(function (response)

    { if (response) { return response; } return fetch(event.request) .then(function (response) { return response; }).catch(function (error) { throw error; }); }) ); });
  157. Worker self.addEventListener("fetch", function (event) { console.log(event.request.url); event.respondWith( caches.match(event.request) .then(function (response)

    { if (response) { return response; } return fetch(event.request) .then(function (response) { return response; }).catch(function (error) { throw error; }); }) ); });
  158. Worker self.addEventListener("fetch", function (event) { console.log(event.request.url); event.respondWith( caches.match(event.request) .then(function (response)

    { if (response) { return response; } return fetch(event.request) .then(function (response) { return response; }).catch(function (error) { throw error; }); }) ); });
  159. Worker self.addEventListener("fetch", function (event) { console.log(event.request.url); event.respondWith( caches.match(event.request) .then(function (response)

    { if (response) { return response; } return fetch(event.request) .then(function (response) { return response; }).catch(function (error) { throw error; }); }) ); });
  160. Worker self.addEventListener("fetch", function (event) { console.log(event.request.url); event.respondWith( caches.match(event.request) .then(function (response)

    { if (response) { return response; } return fetch(event.request) .then(function (response) { return response; }).catch(function (error) { throw error; }); }) ); });
  161. Worker self.addEventListener("fetch", function (event) { console.log(event.request.url); event.respondWith( caches.match(event.request) .then(function (response)

    { if (response) { return response; } return fetch(event.request) .then(function (response) { return response; }).catch(function (error) { throw error; }); }) ); });
  162. @adunkman “fetch” event

  163. @adunkman Programmatic access to browser cache Replaces AppCache Allows custom

    offline logic Is completely encapsulated
  164. None
  165. @adunkman ServiceWorker

  166. @adunkman HTTPS/localhost only Not persistent Not tied to browser context

    Domain events
  167. None
  168. @adunkman There’s more to the web than web pages

  169. getharvest.com/careers dunkman.me/talks/fsf @adunkman