Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: safari/ext/background.js

Issue 29338621: Issue 3788 - Keep track of Safari tabs without canLoad (Closed)
Patch Set: Strip useless "0." prefix from documentIds Created March 18, 2016, 7:52 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | safari/ext/common.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 (function() 18 (function()
19 { 19 {
20 /* Context menus */ 20 /* Context menus */
21 21
22 var contextMenuItems = new ext.PageMap(); 22 var contextMenuItems = new ext.PageMap();
23 var lastContextMenuTab;
23 24
24 var ContextMenus = function(page) 25 var ContextMenus = function(page)
25 { 26 {
26 this._page = page; 27 this._page = page;
27 }; 28 };
28 ContextMenus.prototype = { 29 ContextMenus.prototype = {
29 create: function(item) 30 create: function(item)
30 { 31 {
31 var items = contextMenuItems.get(this._page); 32 var items = contextMenuItems.get(this._page);
32 if (!items) 33 if (!items)
33 contextMenuItems.set(this._page, items = []); 34 contextMenuItems.set(this._page, items = []);
34 35
35 items.push(item); 36 items.push(item);
36 }, 37 },
37 remove: function(item) 38 remove: function(item)
38 { 39 {
39 let items = contextMenuItems.get(this._page); 40 let items = contextMenuItems.get(this._page);
40 if (items) 41 if (items)
41 { 42 {
42 let index = items.indexOf(item); 43 let index = items.indexOf(item);
43 if (index != -1) 44 if (index != -1)
44 items.splice(index, 1); 45 items.splice(index, 1);
45 } 46 }
46 } 47 }
47 }; 48 };
48 49
49 safari.application.addEventListener("contextmenu", function(event) 50 safari.application.addEventListener("contextmenu", function(event)
50 { 51 {
52 lastContextMenuTab = event.target;
53
51 if (!event.userInfo) 54 if (!event.userInfo)
52 return; 55 return;
53 56
54 var pageId = event.userInfo.pageId; 57 var documentId = event.userInfo.documentId;
55 if (!pageId) 58 if (!documentId)
56 return; 59 return;
57 60
58 var page = pages[event.userInfo.pageId]; 61 var page = pages[event.target._documentLookup[documentId].pageId];
59 var items = contextMenuItems.get(page); 62 var items = contextMenuItems.get(page);
60 if (!items) 63 if (!items)
61 return; 64 return;
62 65
63 var context = event.userInfo.tagName; 66 var context = event.userInfo.tagName;
64 if (context == "img") 67 if (context == "img")
65 context = "image"; 68 context = "image";
66 69
67 for (var i = 0; i < items.length; i++) 70 for (var i = 0; i < items.length; i++)
68 { 71 {
69 // Supported contexts are: all, audio, image, video 72 // Supported contexts are: all, audio, image, video
70 var menuItem = items[i]; 73 var menuItem = items[i];
71 if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(co ntext) == -1) 74 if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(co ntext) == -1)
72 continue; 75 continue;
73 76
74 event.contextMenu.appendContextMenuItem(i, menuItem.title); 77 event.contextMenu.appendContextMenuItem(i, menuItem.title);
75 } 78 }
76 }); 79 });
77 80
78 safari.application.addEventListener("command", function(event) 81 safari.application.addEventListener("command", function(event)
79 { 82 {
80 var page = pages[event.userInfo.pageId]; 83 var documentId = event.userInfo.documentId;
84 var page = pages[lastContextMenuTab._documentLookup[documentId].pageId];
81 var items = contextMenuItems.get(page); 85 var items = contextMenuItems.get(page);
82 86
83 items[event.command].onclick(page); 87 items[event.command].onclick(page);
84 }); 88 });
85 89
86 90
87 /* Browser actions */ 91 /* Browser actions */
88 92
89 var toolbarItemProperties = {}; 93 var toolbarItemProperties = {};
90 94
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after
197 this.browserAction = new BrowserAction(this); 201 this.browserAction = new BrowserAction(this);
198 this.contextMenus = new ContextMenus(this); 202 this.contextMenus = new ContextMenus(this);
199 }; 203 };
200 Page.prototype = { 204 Page.prototype = {
201 get url() 205 get url()
202 { 206 {
203 return this._frames[0].url; 207 return this._frames[0].url;
204 }, 208 },
205 sendMessage: function(message, responseCallback) 209 sendMessage: function(message, responseCallback)
206 { 210 {
207 this._messageProxy.sendMessage(message, responseCallback, {pageId: this.id }); 211 var documentIds = [];
212 for (var documentId in this._tab._documentLookup)
213 if (this._tab._documentLookup[documentId].pageId == this.id)
214 documentIds.push(documentId);
215
216 this._messageProxy.sendMessage(message, responseCallback,
217 {targetDocuments: documentIds});
208 } 218 }
209 }; 219 };
210 220
211 ext.getPage = function(id) 221 ext.getPage = function(id)
212 { 222 {
213 return pages[id]; 223 return pages[id];
214 }; 224 };
215 225
216 var isPageActive = function(page) 226 var isPageActive = function(page)
217 { 227 {
218 var tab = page._tab; 228 var tab = page._tab;
219 var win = tab.browserWindow; 229 var win = tab.browserWindow;
220 return win && tab == win.activeTab && page == tab._visiblePage; 230 return win && tab == win.activeTab && page == tab._visiblePage;
221 }; 231 };
222 232
223 var forgetPage = function(id) 233 var forgetPage = function(id)
224 { 234 {
225 ext.pages.onRemoved._dispatch(id); 235 ext.pages.onRemoved._dispatch(id);
226 236
227 ext._removeFromAllPageMaps(id); 237 ext._removeFromAllPageMaps(id);
228 238
229 delete pages[id]._tab._pages[id]; 239 var tab = pages[id]._tab;
240
241 for (var documentId in tab._documentLookup)
242 {
243 if (tab._documentLookup[documentId].pageId == id)
244 delete tab._documentLookup[documentId];
245 }
246
247 delete tab._pages[id];
230 delete pages[id]; 248 delete pages[id];
231 }; 249 };
232 250
233 var replacePage = function(page) 251 var replacePage = function(page)
234 { 252 {
235 var tab = page._tab; 253 var tab = page._tab;
236 tab._visiblePage = page; 254 tab._visiblePage = page;
237 255
238 for (var id in tab._pages) 256 for (var id in tab._pages)
239 { 257 {
240 if (id != page.id) 258 if (id != page.id)
241 forgetPage(id); 259 forgetPage(id);
242 } 260 }
243 261
244 if (isPageActive(page)) 262 if (isPageActive(page))
245 updateToolbarItemForPage(page, tab.browserWindow); 263 updateToolbarItemForPage(page, tab.browserWindow);
246 }; 264 };
247 265
248 var addPage = function(tab, url, prerendered) 266 var addPage = function(tab, url, prerendered)
249 { 267 {
250 var pageId = ++pageCounter; 268 var pageId = ++pageCounter;
251 269
252 if (!('_pages' in tab)) 270 if (!('_pages' in tab))
253 tab._pages = Object.create(null); 271 tab._pages = Object.create(null);
254 272
273 if (!('_documentLookup' in tab))
274 tab._documentLookup = Object.create(null);
275
255 var page = new Page(pageId, tab, url); 276 var page = new Page(pageId, tab, url);
256 pages[pageId] = tab._pages[pageId] = page; 277 pages[pageId] = tab._pages[pageId] = page;
257 278
258 // When a new page is shown, forget the previous page associated 279 // When a new page is shown, forget the previous page associated
259 // with its tab, and reset the toolbar item if necessary. 280 // with its tab, and reset the toolbar item if necessary.
260 // Note that it wouldn't be sufficient to do that when the old 281 // Note that it wouldn't be sufficient to do that when the old
261 // page is unloading, because Safari dispatches window.onunload 282 // page is unloading, because Safari dispatches window.onunload
262 // only when reloading the page or following links, but not when 283 // only when reloading the page or following links, but not when
263 // you enter a new URL in the address bar. 284 // you enter a new URL in the address bar.
264 if (!prerendered) 285 if (!prerendered)
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
333 // use Safari's extension API to detect existing tabs. 354 // use Safari's extension API to detect existing tabs.
334 safari.application.browserWindows.forEach(function(win) 355 safari.application.browserWindows.forEach(function(win)
335 { 356 {
336 for (var i = 0; i < win.tabs.length; i++) 357 for (var i = 0; i < win.tabs.length; i++)
337 { 358 {
338 var tab = win.tabs[i]; 359 var tab = win.tabs[i];
339 var url = tab.url; 360 var url = tab.url;
340 361
341 // For the new tab page the url property is undefined. 362 // For the new tab page the url property is undefined.
342 if (url) 363 if (url)
343 addPage(tab, url, false); 364 {
365 var pageId = addPage(tab, url, false);
366 tab.page.dispatchMessage("requestDocumentId", {pageId: pageId});
367 }
344 } 368 }
345 }); 369 });
346 370
347 371
348 /* Web requests */ 372 /* Web requests */
349 373
350 ext.webRequest = { 374 ext.webRequest = {
351 onBeforeRequest: new ext._EventTarget(), 375 onBeforeRequest: new ext._EventTarget(),
352 handlerBehaviorChanged: function() 376 handlerBehaviorChanged: function()
353 { 377 {
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
418 return function() 442 return function()
419 { 443 {
420 var page = pages[pageId]; 444 var page = pages[pageId];
421 if (!page) 445 if (!page)
422 return; 446 return;
423 447
424 var objects = proxy.cache.get(page); 448 var objects = proxy.cache.get(page);
425 if (!objects) 449 if (!objects)
426 return; 450 return;
427 451
452 var targetDocument;
453 for (var documentId in page._tab._documentLookup)
454 {
455 var result = page._tab._documentLookup[documentId];
456 if (result.pageId == pageId && result.frameId == frameId)
457 {
458 targetDocument = documentId;
459 break;
460 }
461 }
462 if (!targetDocument)
463 return;
464
428 page._tab.page.dispatchMessage("proxyCallback", 465 page._tab.page.dispatchMessage("proxyCallback",
429 { 466 {
430 pageId: pageId, 467 targetDocuments: [targetDocument],
431 frameId: frameId,
432 callbackId: callbackId, 468 callbackId: callbackId,
433 contextId: proxy.registerObject(this, objects), 469 contextId: proxy.registerObject(this, objects),
434 args: proxy.serializeSequence(arguments, objects) 470 args: proxy.serializeSequence(arguments, objects)
435 }); 471 });
436 }; 472 };
437 }, 473 },
438 deserialize: function(spec, objects, pageId, memo) 474 deserialize: function(spec, objects, pageId, frameId, memo)
439 { 475 {
440 switch (spec.type) 476 switch (spec.type)
441 { 477 {
442 case "value": 478 case "value":
443 return spec.value; 479 return spec.value;
444 case "hosted": 480 case "hosted":
445 return objects[spec.objectId]; 481 return objects[spec.objectId];
446 case "callback": 482 case "callback":
447 return this.createCallback(spec.callbackId, pageId, spec.frameId); 483 return this.createCallback(spec.callbackId, pageId, frameId);
448 case "object": 484 case "object":
449 case "array": 485 case "array":
450 if (!memo) 486 if (!memo)
451 memo = {specs: [], objects: []}; 487 memo = {specs: [], objects: []};
452 488
453 var idx = memo.specs.indexOf(spec); 489 var idx = memo.specs.indexOf(spec);
454 if (idx != -1) 490 if (idx != -1)
455 return memo.objects[idx]; 491 return memo.objects[idx];
456 492
457 var obj; 493 var obj;
458 if (spec.type == "array") 494 if (spec.type == "array")
459 obj = []; 495 obj = [];
460 else 496 else
461 obj = {}; 497 obj = {};
462 498
463 memo.specs.push(spec); 499 memo.specs.push(spec);
464 memo.objects.push(obj); 500 memo.objects.push(obj);
465 501
466 if (spec.type == "array") 502 if (spec.type == "array")
467 for (var i = 0; i < spec.items.length; i++) 503 for (var i = 0; i < spec.items.length; i++)
468 obj.push(this.deserialize(spec.items[i], objects, pageId, memo)); 504 obj.push(this.deserialize(spec.items[i], objects,
505 pageId, frameId, memo));
469 else 506 else
470 for (var k in spec.properties) 507 for (var k in spec.properties)
471 obj[k] = this.deserialize(spec.properties[k], objects, pageId, mem o); 508 obj[k] = this.deserialize(spec.properties[k], objects,
509 pageId, frameId, memo);
472 510
473 return obj; 511 return obj;
474 } 512 }
475 }, 513 },
476 getObjectCache: function(page) 514 getObjectCache: function(page)
477 { 515 {
478 var objects = this.cache.get(page); 516 var objects = this.cache.get(page);
479 if (!objects) 517 if (!objects)
480 { 518 {
481 objects = [window]; 519 objects = [window];
(...skipping 21 matching lines...) Expand all
503 var value = obj[message.property]; 541 var value = obj[message.property];
504 } 542 }
505 catch (e) 543 catch (e)
506 { 544 {
507 return this.fail(e); 545 return this.fail(e);
508 } 546 }
509 547
510 return {succeed: true, result: this.serialize(value, objects)}; 548 return {succeed: true, result: this.serialize(value, objects)};
511 case "setProperty": 549 case "setProperty":
512 var obj = objects[message.objectId]; 550 var obj = objects[message.objectId];
513 var value = this.deserialize(message.value, objects, message.pageId); 551 var value = this.deserialize(message.value, objects,
552 message.pageId, message.frameId);
514 553
515 try 554 try
516 { 555 {
517 obj[message.property] = value; 556 obj[message.property] = value;
518 } 557 }
519 catch (e) 558 catch (e)
520 { 559 {
521 return this.fail(e); 560 return this.fail(e);
522 } 561 }
523 562
524 return {succeed: true}; 563 return {succeed: true};
525 case "callFunction": 564 case "callFunction":
526 var func = objects[message.functionId]; 565 var func = objects[message.functionId];
527 var context = objects[message.contextId]; 566 var context = objects[message.contextId];
528 567
529 var args = []; 568 var args = [];
530 for (var i = 0; i < message.args.length; i++) 569 for (var i = 0; i < message.args.length; i++)
531 args.push(this.deserialize(message.args[i], objects, message.pageId) ); 570 args.push(this.deserialize(message.args[i], objects,
571 message.pageId, message.frameId));
532 572
533 try 573 try
534 { 574 {
535 var result = func.apply(context, args); 575 var result = func.apply(context, args);
536 } 576 }
537 catch (e) 577 catch (e)
538 { 578 {
539 return this.fail(e); 579 return this.fail(e);
540 } 580 }
541 581
(...skipping 21 matching lines...) Expand all
563 return objectInfo; 603 return objectInfo;
564 } 604 }
565 } 605 }
566 }; 606 };
567 607
568 608
569 /* Message processing */ 609 /* Message processing */
570 610
571 safari.application.addEventListener("message", function(event) 611 safari.application.addEventListener("message", function(event)
572 { 612 {
613 var tab = event.target;
614 var message = event.message;
615 var sender;
616 if ("documentId" in message && "_documentLookup" in tab)
617 {
618 sender = tab._documentLookup[message.documentId];
619 if (sender)
620 {
621 sender.page = pages[sender.pageId];
622 sender.frame = sender.page._frames[sender.frameId];
623 }
624 }
625
573 switch (event.name) 626 switch (event.name)
574 { 627 {
575 case "canLoad": 628 case "canLoad":
576 switch (event.message.category) 629 switch (message.category)
577 { 630 {
578 case "loading":
579 var tab = event.target;
580 var message = event.message;
581
582 var pageId;
583 var frameId;
584
585 if (message.isTopLevel)
586 {
587 pageId = addPage(tab, message.url, message.isPrerendered);
588 frameId = 0;
589
590 ext.pages.onLoading._dispatch(pages[pageId]);
591 }
592 else
593 {
594 var page;
595 var parentFrame;
596
597 var lastPageId;
598 var lastPage;
599 var lastPageTopLevelFrame;
600
601 // find the parent frame and its page for this sub frame,
602 // by matching its referrer with the URL of frames previously
603 // loaded in the same tab. If there is more than one match,
604 // the most recent loaded page and frame is preferred.
605 for (var curPageId in tab._pages)
606 {
607 var curPage = pages[curPageId];
608
609 for (var i = 0; i < curPage._frames.length; i++)
610 {
611 var curFrame = curPage._frames[i];
612
613 if (curFrame.url.href == message.referrer)
614 {
615 pageId = curPageId;
616 page = curPage;
617 parentFrame = curFrame;
618 }
619
620 if (i == 0)
621 {
622 lastPageId = curPageId;
623 lastPage = curPage;
624 lastPageTopLevelFrame = curFrame;
625 }
626 }
627 }
628
629 // if we can't find the parent frame and its page, fall back to
630 // the page most recently loaded in the tab and its top level fram e
631 if (!page)
632 {
633 pageId = lastPageId;
634 page = lastPage;
635 parentFrame = lastPageTopLevelFrame;
636 }
637
638 frameId = page._frames.length;
639 page._frames.push({url: new URL(message.url), parent: parentFrame} );
640 }
641 event.message = {pageId: pageId, frameId: frameId};
642 break;
643 case "webRequest": 631 case "webRequest":
644 var page = pages[event.message.pageId];
645 var frame = page._frames[event.message.frameId];
646
647 var results = ext.webRequest.onBeforeRequest._dispatch( 632 var results = ext.webRequest.onBeforeRequest._dispatch(
648 new URL(event.message.url, frame.url), 633 new URL(message.url, sender.frame.url),
649 event.message.type, page, frame 634 message.type, sender.page, sender.frame
650 ); 635 );
651 636
652 event.message = (results.indexOf(false) == -1); 637 event.message = (results.indexOf(false) == -1);
653 break; 638 break;
654 case "proxy": 639 case "proxy":
655 event.message = backgroundPageProxy.handleMessage(event.message); 640 message.pageId = sender.pageId;
641 message.frameId = sender.frameId;
642 event.message = backgroundPageProxy.handleMessage(message);
656 break; 643 break;
657 case "request": 644 case "request":
658 var page = pages[event.message.pageId];
659 var sender = {page: page, frame: page._frames[event.message.frameId] };
660
661 var response = null; 645 var response = null;
662 var sendResponse = function(message) { response = message; }; 646 var sendResponse = function(message) { response = message; };
663 647
664 ext.onMessage._dispatch(event.message.payload, sender, sendResponse) ; 648 ext.onMessage._dispatch(message.payload, sender, sendResponse);
665 649
666 event.message = response; 650 event.message = response;
667 break; 651 break;
668 } 652 }
669 break; 653 break;
670 case "request": 654 case "request":
671 var page = pages[event.message.pageId]; 655 sender.page._messageProxy.handleRequest(message, sender);
672 var sender = {page: page, frame: page._frames[event.message.frameId]};
673 page._messageProxy.handleRequest(event.message, sender);
674 break; 656 break;
675 case "response": 657 case "response":
676 pages[event.message.pageId]._messageProxy.handleResponse(event.message); 658 // All documents within a page have the same pageId and that's all we
659 // care about here.
660 var pageId = tab._documentLookup[message.targetDocuments[0]].pageId;
661 pages[pageId]._messageProxy.handleResponse(message);
677 break; 662 break;
678 case "replaced": 663 case "replaced":
679 // when a prerendered page is shown, forget the previous page 664 // when a prerendered page is shown, forget the previous page
680 // associated with its tab, and reset the toolbar item if necessary. 665 // associated with its tab, and reset the toolbar item if necessary.
681 // Note that it wouldn't be sufficient to do that when the old 666 // Note that it wouldn't be sufficient to do that when the old
682 // page is unloading, because Safari dispatches window.onunload 667 // page is unloading, because Safari dispatches window.onunload
683 // only when reloading the page or following links, but not when 668 // only when reloading the page or following links, but not when
684 // the current page is replaced with a prerendered page. 669 // the current page is replaced with a prerendered page.
685 replacePage(pages[event.message.pageId]); 670 replacePage(sender.page);
671 break;
672 case "loading":
673 var pageId;
674 var frameId;
675 var documentId = message.documentId;
676
677 if (message.isTopLevel)
678 {
679 pageId = addPage(tab, message.url, message.isPrerendered);
680 frameId = 0;
681
682 ext.pages.onLoading._dispatch(pages[pageId]);
683 }
684 else
685 {
686 var page;
687 var parentFrame;
688
689 var lastPageId;
690 var lastPage;
691 var lastPageTopLevelFrame;
692
693 // find the parent frame and its page for this sub frame,
694 // by matching its referrer with the URL of frames previously
695 // loaded in the same tab. If there is more than one match,
696 // the most recent loaded page and frame is preferred.
697 for (var curPageId in tab._pages)
698 {
699 var curPage = pages[curPageId];
700
701 for (var i = 0; i < curPage._frames.length; i++)
702 {
703 var curFrame = curPage._frames[i];
704
705 if (curFrame.url.href == message.referrer)
706 {
707 pageId = curPageId;
708 page = curPage;
709 parentFrame = curFrame;
710 }
711
712 if (i == 0)
713 {
714 lastPageId = curPageId;
715 lastPage = curPage;
716 lastPageTopLevelFrame = curFrame;
717 }
718 }
719 }
720
721 // if we can't find the parent frame and its page, fall back to
722 // the page most recently loaded in the tab and its top level frame
723 if (!page)
724 {
725 pageId = lastPageId;
726 page = lastPage;
727 parentFrame = lastPageTopLevelFrame;
728 }
729
730 frameId = page._frames.length;
731 page._frames.push({url: new URL(message.url), parent: parentFrame});
732 }
733
734 tab._documentLookup[documentId] = {pageId: pageId, frameId: frameId};
735 break;
736 case "documentId":
737 tab._documentLookup[message.documentId] = {
738 pageId: message.pageId, frameId: 0
739 };
686 break; 740 break;
687 } 741 }
688 }); 742 });
689 743
690 744
691 /* Storage */ 745 /* Storage */
692 746
693 ext.storage = { 747 ext.storage = {
694 get: function(keys, callback) 748 get: function(keys, callback)
695 { 749 {
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
762 /* Windows */ 816 /* Windows */
763 ext.windows = { 817 ext.windows = {
764 // Safari doesn't provide as rich a windows API as Chrome does, so instead 818 // Safari doesn't provide as rich a windows API as Chrome does, so instead
765 // of chrome.windows.create we have to fall back to just opening a new tab. 819 // of chrome.windows.create we have to fall back to just opening a new tab.
766 create: function(createData, callback) 820 create: function(createData, callback)
767 { 821 {
768 ext.pages.open(createData.url, callback); 822 ext.pages.open(createData.url, callback);
769 } 823 }
770 }; 824 };
771 })(); 825 })();
OLDNEW
« no previous file with comments | « no previous file | safari/ext/common.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld