Beyond The Tab: Executing JavaScript Across Browser Contexts @ Nodevember

Beyond The Tab: Executing JavaScript Across Browser Contexts @ Nodevember

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

November 20, 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. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. @adunkman donate.splcenter.org

  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. @adunkman rest in peace, vine.

  22. @adunkman Standard Browser APIs

  23. None
  24. None
  25. None
  26. None
  27. None
  28. None
  29. None
  30. None
  31. None
  32. None
  33. None
  34. None
  35. None
  36. None
  37. @adunkman

  38. @adunkman

  39. @adunkman Cache-Control

  40. @adunkman

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

  42. @adunkman Browser Contexts

  43. @adunkman Window Sandbox

  44. @adunkman Domain-Level Objects

  45. None
  46. None
  47. @adunkman Why isn’t this better?

  48. @adunkman document.cookie

  49. Bob Dunkman c. 1977

  50. Bob Dunkman c. 2016

  51. @adunkman document.cookie

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

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

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

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

  56. @adunkman document.cookie localStorage

  57. @adunkman QuotaExceededError

  58. @adunkman document.cookie localStorage

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

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

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

    function (event) { ...
  62. None
  63. 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(); } });
  64. 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(); } });
  65. 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
  66. None
  67. @adunkman Between Tabs Outside Tabs

  68. @adunkman WebWorker

  69. @adunkman SharedWorker

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

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

  72. None
  73. None
  74. None
  75. None
  76. %

  77. None
  78. External Page

  79. External Page Harvest Script

  80. External Page Harvest Script

  81. External Page Harvest Script <iframe>

  82. External Page Harvest Script <iframe> SharedWorker

  83. External Page Harvest Script <iframe> SharedWorker WebSocket

  84. None
  85. None
  86. None
  87. None
  88. None
  89. None
  90. None
  91. @adunkman SharedWorker

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

    
 working in Safari
  93. @adunkman SharedWorker

  94. @adunkman ServiceWorker

  95. @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
  96. @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
  97. @adunkman A service worker is an event- driven worker registered

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

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

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

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

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

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

  104. @adunkman “push” event

  105. None
  106. None
  107. None
  108. None
  109. None
  110. None
  111. None
  112. None
  113. @adunkman Notification Service

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

    console.log("fail", err); });
  115. 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
  116. 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)
  117. 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)
  118. 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)
  119. Page navigator.serviceWorker.register("sw.js") .then(function (reg) { console.log("success", reg); }).catch(function (err) {

    console.log("fail", err); });
  120. 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
  121. 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(); });
  122. 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(); });
  123. None
  124. @adunkman “push” event

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

    process event
  126. None
  127. @adunkman “sync” event

  128. None
  129. None
  130. None
  131. None
  132. None
  133. None
  134. 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()); } });
  135. Tab 1 worker.js 1

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

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

  138. worker.js 1 Browser

  139. worker.js 1 Browser

  140. @adunkman “sync” event

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

    or no connectivity
  142. None
  143. @adunkman “fetch” event

  144. None
  145. None
  146. 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; }); }) ); });
  147. 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; }); }) ); });
  148. 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; }); }) ); });
  149. 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; }); }) ); });
  150. 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; }); }) ); });
  151. 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; }); }) ); });
  152. @adunkman “fetch” event

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

    offline logic Is completely encapsulated
  154. None
  155. None
  156. @adunkman push, sync, fetch

  157. @adunkman ServiceWorker

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

    Domain events
  159. None
  160. @adunkman There’s more to the web than web pages

  161. getharvest.com/careers dunkman.me/talks/tabs @adunkman