├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── src
├── images
├── tabber_128.png
├── tabber_16.png
├── tabber_32.png
├── tabber_48.png
├── tabber_green_19.png
├── tabber_green_38.png
├── tabber_red_19.png
├── tabber_red_38.png
├── tabber_ui.png
├── tabber_yellow_19.png
└── tabber_yellow_38.png
├── js
├── externs.js
├── interns.js
├── popup.js
├── sync_phase_handler.js
├── tabber.js
├── tabber_session.js
└── tabs.js
├── manifest.json
└── static
└── popup.html
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chrome Tabber Project
2 |
3 | This repository contains the open source project Chrome Tabber.
4 |
5 | This project uses the Apache license.
6 |
7 | ## What does Chrome Tabber Do?
8 |
9 | Chrome Tabber allows you to share your browser tabs across devices very simply,
10 | using your Google account.
11 |
12 | By default, when you open the Chrome browser, the tabs are initialized to the
13 | same state as when you closed the browser *on that device.*
14 |
15 | Although there is a setting that lets you change that behavior, you cannot
16 | easily restore tabs from another device. The official way to do that in Chrome
17 | is to save your current tabs as a group bookmark (*"Bookmark Open Pages..."*)
18 | and then open that bookmark on another device.
19 |
20 | Chrome Tabber lets you instantly sync your tabs across Chrome browsers on any
21 | device logged into your Google account. It provides several modes of operation
22 | so you can customize this behavior to fit your needs.
23 |
24 | ## How to use Chrome Tabber
25 |
26 | After installing Chrome Tabber it will display a little icon in Chrome's
27 | extension icon area (typically the upper right of the browser). Click this icon
28 | in order see Chrome Tabber's popup UI.
29 |
30 | On Tabber's popup, you can see status, perform manual syncing, or change the
31 | mode of operation. The mode of operation is persistent, and applies to the
32 | *current device* only. That means you can set different modes on different
33 | devices (which is a useful and typical way to set up your devices).
34 |
35 | ### Chrome Tabber Modes
36 |
37 | * Manual: Just like it sounds, in order to save or load tabs, you must click a
38 | button on Tabber's popup UI.
39 |
40 | * Startup Only: Your saved tabs are loaded only when the browser is opened.
41 |
42 | * Auto-Save: Like *Startup Only* but any changes you make on this device are
43 | saved for sharing to other devices.
44 |
45 | * Fully Automatic: Like Auto-Save but Chrome Tabber will also detect changes
46 | made on other devices and automatically load them.
47 |
48 | By setting various modes on your devices, you can support different use cases.
49 | Here are some common scenarios:
50 |
51 | QUESTION: I have one main device and multiple secondary devices. How do I set up
52 | Chrome Tabber so that whenever I start Chrome on a secondary device, it opens
53 | with the same tabs I last used on my main device?
54 |
55 | ANSWER: Set your main device to *Auto-Save* and secondary devices to *Startup
56 | Only.* With this setup, subsequent tabs that you open or close on your secondary
57 | devices do not get reflected back to your main device.
58 |
59 | QUESTION: I have several devices I use. How do I make it so they all show the
60 | same tabs all the time?
61 |
62 | ANSWER: Set all devices to *Fully Automatic*
63 |
64 | QUESTION: I use one device almost all the time, but occasionally I do some work
65 | on another device. How do I make it so that I can move from device to device,
66 | and always 'pick up where I left off?'
67 |
68 | ANSWER: There are several ways to do this:
69 |
70 | 1. Set all devices to *Fully Automatic*
71 |
72 | 1. Set main device to *Fully Automatic* and secondary devices to *Auto-Save*
73 |
74 | 1. Use manual save and load operations to 'move' your work whenever you like.
75 |
76 | Best Practice: Set main device to *Fully Automatic* and secondary devices to
77 | *Startup Only.* Once you are done working on the secondary device, manually
78 | click the "Save the Tabs I have Now" button.
79 |
80 | # NOTE
81 | This is not an official Google product.
82 |
--------------------------------------------------------------------------------
/src/images/tabber_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_128.png
--------------------------------------------------------------------------------
/src/images/tabber_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_16.png
--------------------------------------------------------------------------------
/src/images/tabber_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_32.png
--------------------------------------------------------------------------------
/src/images/tabber_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_48.png
--------------------------------------------------------------------------------
/src/images/tabber_green_19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_green_19.png
--------------------------------------------------------------------------------
/src/images/tabber_green_38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_green_38.png
--------------------------------------------------------------------------------
/src/images/tabber_red_19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_red_19.png
--------------------------------------------------------------------------------
/src/images/tabber_red_38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_red_38.png
--------------------------------------------------------------------------------
/src/images/tabber_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_ui.png
--------------------------------------------------------------------------------
/src/images/tabber_yellow_19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_yellow_19.png
--------------------------------------------------------------------------------
/src/images/tabber_yellow_38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/chrome-tabber/f0b4529e4550fd094d79c5c6e1716131119eee21/src/images/tabber_yellow_38.png
--------------------------------------------------------------------------------
/src/js/externs.js:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | /**
18 | * Define the Tabber interface which provides the external tabber access.
19 | * All external Tabber access goes through a singleton Tabber object which
20 | * implements this interface.
21 | * @fileoverview
22 | */
23 |
24 | /**
25 | * Define the actual Tabber API interface.
26 | * @interface
27 | */
28 | function TabberApi() {}
29 |
30 | /**
31 | * @type TabberApi.type.mode
32 | */
33 | TabberApi.prototype.mode;
34 |
35 | /**
36 | * @type TabberApi.type.state
37 | */
38 | TabberApi.prototype.state;
39 |
40 | /**
41 | * Save the current local tabs.
42 | */
43 | TabberApi.prototype.saveLocalToRemote = function() {};
44 |
45 | /**
46 | * Set the browser tabs to match the saved set.
47 | */
48 | TabberApi.prototype.syncBrowserFromRemote = function() {};
49 |
50 | /**
51 | * Setting Tabber options.
52 | * @param {TabberApi.type.config} config - The desired config option values.
53 | * @return {undefined}
54 | */
55 | TabberApi.prototype.setOptions = function(config) {};
56 |
57 | /**
58 | * Getting Tabber status.
59 | * @return {TabberApi.type.status}
60 | */
61 | TabberApi.prototype.getStatus = function() {};
62 |
63 | /**
64 | * Define type property and associated TabberApi types.
65 | */
66 | TabberApi.type = function() {};
67 |
68 | /**
69 | * Define information types.
70 | * @typedef {{
71 | * OK: number,
72 | * WARN: number,
73 | * ERR: number
74 | * }}
75 | */
76 | TabberApi.type.state;
77 |
78 | /**
79 | * Define operational modes.
80 | * @typedef {{
81 | * AUTOSYNC: string,
82 | * AUTOSTART: string,
83 | * AUTOSAVE: string,
84 | * MANUAL: string
85 | * }}
86 | */
87 | TabberApi.type.mode;
88 |
89 | /**
90 | * Define operational configuration options.
91 | * @typedef {{
92 | * mode: (undefined|string),
93 | * debug: (undefined|boolean)
94 | * }}
95 | */
96 | TabberApi.type.config;
97 |
98 | /**
99 | * Define information types.
100 | * @typedef {{
101 | * state: string,
102 | * msg: string
103 | * }}
104 | */
105 | TabberApi.type.sync_state;
106 |
107 | /**
108 | * Define information types.
109 | * @typedef {{
110 | * options: TabberApi.type.config,
111 | * sync: TabberApi.type.sync_state,
112 | * remote_time: string
113 | * }}
114 | */
115 | TabberApi.type.status;
116 |
117 |
--------------------------------------------------------------------------------
/src/js/interns.js:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | /**
18 | * Define the Tabber internal types and classes which are not exposed to the
19 | * popup UI, but still need to be name-protected or type checked inside Tabber.
20 | * @fileoverview
21 | */
22 |
23 | /**
24 | * Define the TabberInt object which defines the internal types and methods.
25 | * @interface
26 | */
27 | function TabberInt() {}
28 |
29 | /**
30 | * Define Tab object fields we care about. The only required fields are the
31 | * url and the index (which are also the only ones that can be specified to
32 | * chrome when creating a new tab).
33 | * @typedef {{
34 | * url: string,
35 | * index: string,
36 | * id: (string|undefined),
37 | * windowId: (number|undefined),
38 | * active: (boolean|undefined),
39 | * title: (string|undefined)
40 | * }}
41 | */
42 | TabberInt.type.Tab;
43 |
44 | /**
45 | * Define tabdiff object fields.
46 | * @typedef {{
47 | * major: string,
48 | * minor: string,
49 | * err: boolean
50 | * }}
51 | */
52 | TabberInt.type.TabDiff;
53 |
54 | /**
55 | * Define the TabberSession object which holds saved tabs data.
56 | * @interface
57 | */
58 | TabberInt.TabberSession = function() {};
59 |
60 | /** @type {string} */
61 | TabberInt.TabberSession.prototype.description;
62 |
63 | /** @type {number} */
64 | TabberInt.TabberSession.prototype.generation;
65 |
66 | /** @type {Object|number} */
67 | TabberInt.TabberSession.prototype.numtabs;
68 |
69 | /** @type {Array} */
70 | TabberInt.TabberSession.prototype.tabs;
71 |
72 | /** @type {number|boolean} */
73 | TabberInt.TabberSession.prototype.updateTime;
74 |
75 | /**
76 | * Helper to print session info to console.
77 | * @param {string} label - Label to tag the output with.
78 | */
79 | TabberInt.TabberSession.prototype.printSession = function(label) {};
80 |
81 | /**
82 | * Return the last touch time as a user string. If this session has not yet
83 | * been initialized, it returns an empty string.
84 | * @return {string}
85 | */
86 | TabberInt.TabberSession.prototype.getTimeString = function() {};
87 |
88 |
--------------------------------------------------------------------------------
/src/js/popup.js:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // The popup script provides the UI for the current Tabber state
18 |
19 | /**
20 | * Gets Tabber access from the background page.
21 | * @return {TabberApi}
22 | */
23 | function getTabber() {
24 | var bgPage = chrome.extension.getBackgroundPage();
25 | if (!bgPage) {
26 | // Really shouldn't happen
27 | alert('Chrome error accessing background page!\n' +
28 | 'Re-install Tabber and/or restart Chrome!');
29 | }
30 | /**
31 | * Get the background page's Tabber singleton.
32 | * @return {TabberApi}
33 | */
34 | var tabber = bgPage['Tabber'];
35 | if (!tabber) {
36 | // Really shouldn't happen
37 | alert('Tabber not found! Reload Tabber extension and try again.');
38 | }
39 | return tabber;
40 | }
41 |
42 | /**
43 | * UI event handler to grab the current tabs and save them.
44 | */
45 | function onSave() {
46 | console.log('Trying to do a Tabber.saveLocalToRemote');
47 | getTabber()['saveLocalToRemote']();
48 | console.log('Tabber.saveLocalToRemote call done');
49 | // Dismiss our popup.
50 | window.close();
51 | }
52 |
53 | /**
54 | * UI event handler to grab the saved tabs and make the browser match.
55 | */
56 | function onRestore() {
57 | console.log('Trying to do a Tabber.syncBrowserFromRemote');
58 | getTabber()['syncBrowserFromRemote']();
59 | console.log('Tabber.syncBrowserFromRemote call done');
60 | // Dismiss our popup.
61 | window.close();
62 | }
63 |
64 | /**
65 | * UI event handler to merge the saved tabs with the current browser tabs.
66 | */
67 | function onMerge() {
68 | console.log('Trying to do a Tabber.mergeBrowserWithRemote');
69 | // TODO: enable this when ready
70 | // getTabber()['mergeBrowserWithRemote']();
71 | // Dismiss our popup.
72 | window.close();
73 | }
74 |
75 | /**
76 | * Swap the tabs in the saved session with current browser tabs
77 | */
78 | function onSwap() {
79 | console.log('Trying to do a Tabber.swapBrowserWithRemote');
80 | // TODO: enable this when ready
81 | // getTabber()['mergeBrowserWithRemote']();
82 | // Dismiss our popup.
83 | window.close();
84 | }
85 |
86 | /**
87 | * Event handler to set the Tabber mode from the UI.
88 | */
89 | function onManualMode() {
90 | var tbr = getTabber();
91 | tbr.setOptions({mode: tbr.mode.MANUAL});
92 | }
93 |
94 | /**
95 | * Event handler to set the Tabber mode from the UI.
96 | */
97 | function onAutostartMode() {
98 | var tbr = getTabber();
99 | tbr.setOptions({mode: tbr.mode.AUTOSTART});
100 | }
101 |
102 | /**
103 | * Event handler to set the Tabber mode from the UI.
104 | */
105 | function onAutosaveMode() {
106 | var tbr = getTabber();
107 | tbr.setOptions({mode: tbr.mode.AUTOSAVE});
108 | }
109 |
110 | /**
111 | * Event handler to set the Tabber mode from the UI.
112 | */
113 | function onAutosyncMode() {
114 | var tbr = getTabber();
115 | tbr.setOptions({mode: tbr.mode.AUTOSYNC});
116 | }
117 |
118 | /**
119 | * Event handler that set Tabber debug mode from UI.
120 | */
121 | function onDebugMode() {
122 | // Get new debug mode state
123 | /** type {boolean} */
124 | var state = document.getElementById('debug_mode').checked;
125 | getTabber().setOptions({debug: state});
126 | }
127 |
128 | // Once the popup page is loaded, finish init
129 | document.addEventListener('DOMContentLoaded', function() {
130 | /**
131 | * @type {TabberApi}
132 | */
133 | var tbr = getTabber();
134 | var status = tbr.getStatus();
135 | // init our UI
136 | if (status.options.mode == 'autosync') {
137 | document.getElementById('autosync').checked = true;
138 | } else if (status.options.mode == 'autosave') {
139 | document.getElementById('autosave').checked = true;
140 | } else if (status.options.mode == 'autostart') {
141 | document.getElementById('autostart').checked = true;
142 | } else {
143 | document.getElementById('manual').checked = true;
144 | }
145 | document.getElementById('debug_mode').checked = status.options.debug;
146 |
147 | // set our status message
148 | var diff = document.getElementById('diff');
149 | // Show Tabber sync message
150 | diff.textContent = status.sync.msg;
151 | // set text field color by status
152 | if (status.sync.state == tbr.state.OK) {
153 | diff.style.backgroundColor = 'lightgreen';
154 | } else if (status.sync.state == tbr.state.WARN) {
155 | diff.style.backgroundColor = 'yellow';
156 | } else {
157 | diff.style.backgroundColor = 'salmon';
158 | }
159 | // If we have a remote session, show its' timestamp.
160 | // Also, the ability to restore is based on a valid remote session.
161 | if (status['remote_time']) {
162 | document.getElementById('timestamp').textContent = 'Saved session from: ' +
163 | status['remote_time'];
164 | document.getElementById('restore').disabled = false;
165 | } else {
166 | document.getElementById('timestamp').textContent = 'No saved session available';
167 | document.getElementById('restore').disabled = true;
168 | }
169 |
170 | // TODO: add merge capability when supported by Tabber
171 | document.getElementById('merge').disabled = true;
172 |
173 | // TODO: add swap capability when supported by Tabber
174 | document.getElementById('swap').disabled = true;
175 |
176 | // TODO: enable full autosync capability when better tested
177 | // document.getElementById('autosync').disabled = true;
178 |
179 | // hook our action functions to UI elements
180 | document.getElementById('restore').addEventListener('click', onRestore);
181 | document.getElementById('save').addEventListener('click', onSave);
182 | document.getElementById('merge').addEventListener('click', onMerge);
183 | document.getElementById('swap').addEventListener('click', onSwap);
184 | document.getElementById('manual').addEventListener('click', onManualMode);
185 | document.getElementById('autostart').addEventListener('click', onAutostartMode);
186 | document.getElementById('autosave').addEventListener('click', onAutosaveMode);
187 | document.getElementById('autosync').addEventListener('click', onAutosyncMode);
188 | document.getElementById('debug_mode').addEventListener('click', onDebugMode);
189 | });
190 |
191 |
192 |
193 |
194 |
--------------------------------------------------------------------------------
/src/js/sync_phase_handler.js:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview
19 | * sync_phase_handler.js - Provides a management mechanism for serializing a
20 | * sequence of asynchronous chrome API calls, and kicking off the subsequent
21 | * processing when the last call is complete.
22 | */
23 |
24 | goog.provide('TabberInternal.SyncPhaseHandler');
25 |
26 | /**
27 | * Private globals
28 | */
29 |
30 | /**
31 | * @private
32 | * @type {number}
33 | */
34 | var change_count_;
35 |
36 | /**
37 | * @private
38 | * @type {boolean}
39 | */
40 | var pending_changes;
41 |
42 | /**
43 | * @private
44 | * @type {function()}
45 | */
46 | var completionCallback;
47 |
48 | /**
49 | * @private
50 | * @type {?function(*, Array<*>)}
51 | */
52 | var stepCallback;
53 |
54 | /**
55 | * @private
56 | * @type {?*}
57 | */
58 | var stepCallbackContext;
59 |
60 |
61 | /**
62 | * Initialize the state of the SyncPhaseHandler for a new phase.
63 | * @param {function()} callback - The function to call when all chrome callbacks
64 | * are complete.
65 | */
66 | TabberInternal.SyncPhaseHandler.initHandler = function(callback) {
67 | change_count_ = 0;
68 | pending_changes = true;
69 | completionCallback = callback;
70 | };
71 |
72 | /**
73 | * Register a callback for the next doStep completion.
74 | * @param {function(*, Array<*>)} callback - called after next doStep.
75 | * @param {*} ctx - Context passed to callback function.
76 | * @return {undefined}
77 | */
78 | TabberInternal.SyncPhaseHandler.setDoStepCallback = function(callback, ctx) {
79 | // consoleLog('setDoStepCallback callback = ' + callback);
80 | // consoleLog('setDoStepCallback ctx = ' + JSON.stringify(ctx));
81 | stepCallback = callback;
82 | stepCallbackContext = ctx;
83 | };
84 |
85 | /**
86 | * Submit a phase step function call. Func must be of the form:
87 | * function func(arg1, arg2, ..., callback);
88 | * @param {!function(...)} chromeFunction - Required chrome function to call.
89 | * @param {...} arglist - Zero to many arbitrary optional args.
90 | * @return {undefined}
91 | */
92 | TabberInternal.SyncPhaseHandler.doStep = function(chromeFunction, arglist) {
93 | // consoleLog('doStep called w/' + arguments.length + ' args');
94 | // Convert arguments to an args Array so we can manipulate it
95 | var args = (arguments.length === 1 ? [arguments[0]] :
96 | Array.apply(null, arguments));
97 | // The function is the first argument
98 | var func = args.shift();
99 | // The function is expected to take a callback as the last argument
100 | args.push(TabberInternal.SyncPhaseHandler.changeDone);
101 | // Increment for our pending func callback
102 | change_count_++;
103 | // call the func
104 | /*
105 | for (var a = 0; a < args.length; a++) {
106 | if (a == args.length - 1) {
107 | consoleLog('chrome func arg ' + a + ': ' + args[a]);
108 | } else {
109 | consoleLog('chrome func arg ' + a + ': ' + JSON.stringify(args[a]));
110 | }
111 | }
112 | */
113 | chromeFunction.apply(null, args);
114 | };
115 |
116 |
117 | /**
118 | * Called by Chrome as API callback.
119 | * @param {*} arglist - Whatever chrome passes to the callback.
120 | * @return {undefined}
121 | */
122 | TabberInternal.SyncPhaseHandler.changeDone = function(arglist) {
123 | // consoleLog('changeDone gets ' + arguments.length + ' args, first: ' +
124 | // JSON.stringify(arglist));
125 |
126 | // First call the step callback if applicable.
127 | if (stepCallback) {
128 | // consoleLog('On changeDone, calling ' + stepCallback);
129 | // Convert chrome callback arguments to an Array.
130 | var args = (arguments.length === 1 ? [arguments[0]] :
131 | Array.apply(null, arguments));
132 | // Save the callback info locally, so we can clear it before the call.
133 | var loc_stepCallback = stepCallback;
134 | var loc_stepCallbackContext = stepCallbackContext;
135 | // Reset the global callback info.
136 | stepCallback = null;
137 | stepCallbackContext = null;
138 | // Pass it to the callback along with his preset context.
139 | loc_stepCallback(loc_stepCallbackContext, args);
140 | }
141 | /*
142 | consoleLog('On changeDone, SyncPhaseHandle.change_count_=' +
143 | change_count_);
144 | consoleLog('On changeDone, SyncPhaseHandle.pending_changes=' +
145 | pending_changes);
146 | */
147 | // decrement change count (should never go negative)
148 | if (change_count_ > 0) {
149 | change_count_--;
150 | }
151 | // if it reached zero and no more changes are pending
152 | if ((change_count_ == 0) && !pending_changes) {
153 | // then call the completion callback
154 | // consoleLog('SyncPhaseHandler calling completion');
155 | completionCallback();
156 | }
157 | };
158 |
159 |
160 | /**
161 | * Finalize this phase. Called after all steps are submitted.
162 | * @return {undefined}
163 | */
164 | TabberInternal.SyncPhaseHandler.finalize = function() {
165 | // consoleLog('SyncPhaseHandler.finalize called (cc=' + change_count_);
166 | // The last change completion will move to the next phase
167 | pending_changes = false;
168 | // If last change already completed, we can move on here
169 | if (change_count_ == 0) {
170 | TabberInternal.SyncPhaseHandler.changeDone(null);
171 | }
172 | };
173 |
174 |
--------------------------------------------------------------------------------
/src/js/tabber.js:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | https://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | /**
18 | * @fileoverview
19 | * tabber.js - Provides the main Tabber logic for managing Chrome tabs on
20 | * behalf of a Google user.
21 | *
22 | * Notes:
23 | * - This script is intended to run in the background page.
24 | * - Herein the term "browser session" does not refer to a Chrome "sessions",
25 | * which is just a single tab or single-tabbed Window. Instead, it refers to
26 | * the set of tabs which comprise the state of a browser instance.
27 | *
28 | * Overview
29 | * ========
30 | * Tabber helps keep the local browser session in sync with a remote session
31 | * saved online. A session has a list of open tabs, a changestamp, and a
32 | * human-friendly description, which will (eventually) allow the user to
33 | * select/navigate among multiple sessions.
34 | *
35 | * Basic UI
36 | * ========
37 | * Tabber has a status icon (Chrome browse action icon) and a popup page.
38 | *
39 | * The popup page allows the user to perform a few basic Tabber tasks:
40 | * - Set the operational mode (explained below)
41 | * - Perform a manual Save or Restore
42 | *
43 | * The popup also shows a summary of the saved session, including number of
44 | * tabs, and whether it is newer or older than the local one.
45 | *
46 | * The status icon shows status as:
47 | * RED: there is some problem communicating with Chrome or Google
48 | * YELLOW: A sync is needed or is pending.
49 | * GREEN: Current session is properly sync'd with remote
50 | *
51 | * Automatic vs. Manual Operation
52 | * ==============================
53 | * In order to make sync useful yet non-invasive, there are several modes
54 | * of operation that control when/if Tabber automatically tries to sync the
55 | * browser and session states.
56 | *
57 | * MANUAL: No session sync is performed unless user requests it from the UI.
58 | * AUTOSTART: Browser is sync'd to saved session only at Tabber/Browser start.
59 | * AUTOSAVE: Like AUTOSTART, but browser changes are automatically saved.
60 | * AUTOSYNC: Like AUTOSAVE, but the browser is automatically sync'd to any
61 | * remote changes made to the saved session (from another machine).
62 | *
63 | * Internal Operation
64 | * ==================
65 | * Tabber saves or restores the Chrome tabs using the Chrome.storage.sync API,
66 | * which stores extension data in the user's Google account. We don't try to
67 | * sync every move the user makes (i.e. scrolling, zooming, etc.), but we do
68 | * keep track of each tab's url, index, title, and active status.
69 | * When comparing the browser to a saved session, there are three possible
70 | * levels of severity for differences, with Tabber only reporting the most
71 | * severe difference:
72 | *
73 | * Major: A major difference is indicated to the user, and provokes an
74 | * auto-sync operation. An example of a major difference would be
75 | * mismatched tab urls.
76 | * Minor: A minor" difference is not indicated to the user, but still provokes
77 | * an auto-sync. These are differences that may be visible, but not
78 | * substantial. An example of a minor difference would be a mismatch
79 | * of active tabs.
80 | *
81 | * Auto-save operations are done in a time-delayed fashion to avoid
82 | * rapid/repeated online updates. Tabber will wait for at least a few seconds
83 | * of inactivity before initiating any compare or sync operations.
84 | *
85 | */
86 |
87 | goog.require('TabberInternal.TabberSession');
88 |
89 | /**
90 | * Global debug output control
91 | * @type {boolean}
92 | */
93 | var debug = false;
94 |
95 | /**
96 | * Convert a set of tabs into human-readable description.
97 | * @param {Array