Beyond The Tab: Executing JavaScript Across Browser Contexts @ All Things Open

Beyond The Tab: Executing JavaScript Across Browser Contexts @ All Things Open

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

October 26, 2016
Tweet

Transcript

  1. @adunkman Executing JavaScript Across Browser Contexts

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

  5. @adunkman Between Tabs Outside Tabs

  6. None
  7. Washington, DC

  8. Pittsburgh, PA

  9. Los Angeles, CA

  10. New Paltz, NY

  11. Mayne Island, BC

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

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

  17. @adunkman Open access to learning resources

  18. None
  19. None
  20. None
  21. @adunkman confa11y.com

  22. @adunkman confally.com

  23. @adunkman a11yconf.com

  24. @adunkman allyconf.com

  25. None
  26. None
  27. @adunkman Pull Request Opportunities

  28. None
  29. @adunkman Standard Browser APIs

  30. None
  31. None
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. None
  39. None
  40. None
  41. None
  42. None
  43. @adunkman

  44. @adunkman

  45. @adunkman Cache-Control

  46. @adunkman

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

  48. @adunkman Browser Contexts

  49. @adunkman Window Sandbox

  50. @adunkman Domain-Level Objects

  51. None
  52. None
  53. @adunkman Why isn’t this better?

  54. @adunkman document.cookie

  55. Bob Dunkman c. 1977

  56. Bob Dunkman c. 2016

  57. @adunkman document.cookie

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

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

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

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

  62. @adunkman document.cookie localStorage

  63. @adunkman QuotaExceededError

  64. @adunkman document.cookie localStorage

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

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

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

    function (event) { ...
  68. None
  69. 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(); } });
  70. 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(); } });
  71. 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
  72. None
  73. @adunkman Between Tabs Outside Tabs

  74. @adunkman WebWorker

  75. @adunkman SharedWorker

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

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

  78. None
  79. None
  80. None
  81. None
  82. $

  83. None
  84. External Page

  85. External Page Harvest Script

  86. External Page Harvest Script

  87. External Page Harvest Script <iframe>

  88. External Page Harvest Script <iframe> SharedWorker

  89. External Page Harvest Script <iframe> SharedWorker WebSocket

  90. None
  91. None
  92. None
  93. None
  94. None
  95. None
  96. None
  97. @adunkman SharedWorker

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

    
 working in Safari
  99. @adunkman SharedWorker

  100. @adunkman ServiceWorker

  101. @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
  102. @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
  103. @adunkman A service worker is an event- driven worker registered

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

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

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

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

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

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

  110. @adunkman “push” event

  111. None
  112. None
  113. None
  114. None
  115. None
  116. None
  117. None
  118. None
  119. @adunkman Notification Service

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

    console.log("fail", err); });
  121. 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
  122. 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)
  123. 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)
  124. 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)
  125. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); });
  126. 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
  127. 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(); });
  128. 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(); });
  129. None
  130. @adunkman “push” event

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

    process event
  132. None
  133. @adunkman “sync” event

  134. None
  135. None
  136. None
  137. None
  138. None
  139. None
  140. 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()); } });
  141. Tab 1 worker.js 1

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

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

  144. worker.js 1 Browser

  145. worker.js 1 Browser

  146. @adunkman “sync” event

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

    or no connectivity
  148. None
  149. @adunkman “fetch” event

  150. None
  151. None
  152. 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; }); }) ); });
  153. 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; }); }) ); });
  154. 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; }); }) ); });
  155. 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; }); }) ); });
  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. @adunkman “fetch” event

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

    offline logic Is completely encapsulated
  160. None
  161. None
  162. @adunkman push, sync, fetch

  163. @adunkman ServiceWorker

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

    Domain events
  165. None
  166. @adunkman There’s more to the web than web pages

  167. getharvest.com/careers dunkman.me/talks/ato @adunkman