| OLD | NEW | 
|    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-2017 eyeo GmbH |    3  * Copyright (C) 2006-2017 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 "use strict"; |   18 "use strict"; | 
|   19  |   19  | 
 |   20 (function() | 
|   20 { |   21 { | 
|   21   /* Pages */ |   22   /* Pages */ | 
|   22  |   23  | 
|   23   let Page = ext.Page = function(tab) |   24   let Page = ext.Page = function(tab) | 
|   24   { |   25   { | 
|   25     this.id = tab.id; |   26     this.id = tab.id; | 
|   26     this._url = tab.url && new URL(tab.url); |   27     this._url = tab.url && new URL(tab.url); | 
|   27  |   28  | 
|   28     this.browserAction = new BrowserAction(tab.id); |   29     this.browserAction = new BrowserAction(tab.id); | 
|   29     this.contextMenus = new ContextMenus(this); |   30     this.contextMenus = new ContextMenus(this); | 
| (...skipping 20 matching lines...) Expand all  Loading... | 
|   50     sendMessage(message, responseCallback) |   51     sendMessage(message, responseCallback) | 
|   51     { |   52     { | 
|   52       chrome.tabs.sendMessage(this.id, message, responseCallback); |   53       chrome.tabs.sendMessage(this.id, message, responseCallback); | 
|   53     } |   54     } | 
|   54   }; |   55   }; | 
|   55  |   56  | 
|   56   ext.getPage = id => new Page({id: parseInt(id, 10)}); |   57   ext.getPage = id => new Page({id: parseInt(id, 10)}); | 
|   57  |   58  | 
|   58   function afterTabLoaded(callback) |   59   function afterTabLoaded(callback) | 
|   59   { |   60   { | 
|   60      return openedTab => |   61     return openedTab => | 
|   61      { |   62     { | 
|   62        let onUpdated = (tabId, changeInfo, tab) => |   63       let onUpdated = (tabId, changeInfo, tab) => | 
|   63        { |   64       { | 
|   64          if (tabId == openedTab.id && changeInfo.status == "complete") |   65         if (tabId == openedTab.id && changeInfo.status == "complete") | 
|   65          { |   66         { | 
|   66            chrome.tabs.onUpdated.removeListener(onUpdated); |   67           chrome.tabs.onUpdated.removeListener(onUpdated); | 
|   67            callback(new Page(openedTab)); |   68           callback(new Page(openedTab)); | 
|   68          } |   69         } | 
|   69        }; |   70       }; | 
|   70        chrome.tabs.onUpdated.addListener(onUpdated); |   71       chrome.tabs.onUpdated.addListener(onUpdated); | 
|   71      }; |   72     }; | 
|   72   } |   73   } | 
|   73  |   74  | 
|   74   ext.pages = { |   75   ext.pages = { | 
|   75     open(url, callback) |   76     open(url, callback) | 
|   76     { |   77     { | 
|   77       chrome.tabs.create({url: url}, callback && afterTabLoaded(callback)); |   78       chrome.tabs.create({url}, callback && afterTabLoaded(callback)); | 
|   78     }, |   79     }, | 
|   79     query(info, callback) |   80     query(info, callback) | 
|   80     { |   81     { | 
|   81       let rawInfo = {}; |   82       let rawInfo = {}; | 
|   82       for (let property in info) |   83       for (let property in info) | 
|   83       { |   84       { | 
|   84         switch (property) |   85         switch (property) | 
|   85         { |   86         { | 
|   86           case "active": |   87           case "active": | 
|   87           case "lastFocusedWindow": |   88           case "lastFocusedWindow": | 
| (...skipping 27 matching lines...) Expand all  Loading... | 
|  115     if (!frame) |  116     if (!frame) | 
|  116       frame = frames[frameId] = {}; |  117       frame = frames[frameId] = {}; | 
|  117  |  118  | 
|  118     return frame; |  119     return frame; | 
|  119   } |  120   } | 
|  120  |  121  | 
|  121   function updatePageFrameStructure(frameId, tabId, url, parentFrameId) |  122   function updatePageFrameStructure(frameId, tabId, url, parentFrameId) | 
|  122   { |  123   { | 
|  123     if (frameId == 0) |  124     if (frameId == 0) | 
|  124     { |  125     { | 
|  125       let page = new Page({id: tabId, url: url}); |  126       let page = new Page({id: tabId, url}); | 
|  126  |  127  | 
|  127       ext._removeFromAllPageMaps(tabId); |  128       ext._removeFromAllPageMaps(tabId); | 
|  128  |  129  | 
|  129       chrome.tabs.get(tabId, () => |  130       chrome.tabs.get(tabId, () => | 
|  130       { |  131       { | 
|  131         // If the tab is prerendered, chrome.tabs.get() sets |  132         // If the tab is prerendered, chrome.tabs.get() sets | 
|  132         // chrome.runtime.lastError and we have to dispatch the onLoading event, |  133         // chrome.runtime.lastError and we have to dispatch the onLoading event, | 
|  133         // since the onUpdated event isn't dispatched for prerendered tabs. |  134         // since the onUpdated event isn't dispatched for prerendered tabs. | 
|  134         // However, we have to keep relying on the unUpdated event for tabs that |  135         // However, we have to keep relying on the unUpdated event for tabs that | 
|  135         // are already visible. Otherwise browser action changes get overridden |  136         // are already visible. Otherwise browser action changes get overridden | 
|  136         // when Chrome automatically resets them on navigation. |  137         // when Chrome automatically resets them on navigation. | 
|  137         if (chrome.runtime.lastError) |  138         if (chrome.runtime.lastError) | 
|  138           ext.pages.onLoading._dispatch(page); |  139           ext.pages.onLoading._dispatch(page); | 
|  139       }); |  140       }); | 
|  140     } |  141     } | 
|  141  |  142  | 
|  142     // Update frame parent and URL in frame structure |  143     // Update frame parent and URL in frame structure | 
|  143     let frame = createFrame(tabId, frameId); |  144     let frame = createFrame(tabId, frameId); | 
|  144     frame.url = new URL(url); |  145     frame.url = new URL(url); | 
|  145     frame.parent = framesOfTabs[tabId][parentFrameId] || null; |  146     frame.parent = framesOfTabs[tabId][parentFrameId] || null; | 
|  146   }; |  147   } | 
|  147  |  148  | 
|  148   chrome.webRequest.onHeadersReceived.addListener(details => |  149   chrome.webRequest.onHeadersReceived.addListener(details => | 
|  149   { |  150   { | 
|  150     // We have to update the frame structure when switching to a new |  151     // We have to update the frame structure when switching to a new | 
|  151     // document, so that we process any further requests made by that |  152     // document, so that we process any further requests made by that | 
|  152     // document in the right context. Unfortunately, we cannot rely |  153     // document in the right context. Unfortunately, we cannot rely | 
|  153     // on webNavigation.onCommitted since it isn't guaranteed to fire |  154     // on webNavigation.onCommitted since it isn't guaranteed to fire | 
|  154     // before any subresources start downloading[1]. As an |  155     // before any subresources start downloading[1]. As an | 
|  155     // alternative we use webRequest.onHeadersReceived for HTTP(S) |  156     // alternative we use webRequest.onHeadersReceived for HTTP(S) | 
|  156     // URLs, being careful to ignore any responses that won't cause |  157     // URLs, being careful to ignore any responses that won't cause | 
| (...skipping 25 matching lines...) Expand all  Loading... | 
|  182       // "Content-Disposition" with a valid and non-empty value other |  183       // "Content-Disposition" with a valid and non-empty value other | 
|  183       // than "inline". |  184       // than "inline". | 
|  184       // https://chromium.googlesource.com/chromium/src/+/02d3f50b/content/brows
     er/loader/mime_sniffing_resource_handler.cc#534 |  185       // https://chromium.googlesource.com/chromium/src/+/02d3f50b/content/brows
     er/loader/mime_sniffing_resource_handler.cc#534 | 
|  185       // https://chromium.googlesource.com/chromium/src/+/02d3f50b/net/http/http
     _content_disposition.cc#374 |  186       // https://chromium.googlesource.com/chromium/src/+/02d3f50b/net/http/http
     _content_disposition.cc#374 | 
|  186       // https://chromium.googlesource.com/chromium/src/+/16e2688e/net/http/http
     _util.cc#431 |  187       // https://chromium.googlesource.com/chromium/src/+/16e2688e/net/http/http
     _util.cc#431 | 
|  187       if (headerName == "content-disposition") |  188       if (headerName == "content-disposition") | 
|  188       { |  189       { | 
|  189         let disposition = header.value.split(";")[0].replace(/[ \t]+$/, ""); |  190         let disposition = header.value.split(";")[0].replace(/[ \t]+$/, ""); | 
|  190         if (disposition.toLowerCase() != "inline" && |  191         if (disposition.toLowerCase() != "inline" && | 
|  191             /^[\x21-\x7E]+$/.test(disposition) && |  192             /^[\x21-\x7E]+$/.test(disposition) && | 
|  192             !/[()<>@,;:\\"/\[\]?={}]/.test(disposition)) |  193             !/[()<>@,;:\\"/[\]?={}]/.test(disposition)) | 
|  193           return; |  194           return; | 
|  194       } |  195       } | 
|  195  |  196  | 
|  196       // The value of the "Content-Type" header also determines if Chrome will |  197       // The value of the "Content-Type" header also determines if Chrome will | 
|  197       // initiate a download, or otherwise how the response will be rendered. |  198       // initiate a download, or otherwise how the response will be rendered. | 
|  198       // We only need to consider responses which will result in a navigation |  199       // We only need to consider responses which will result in a navigation | 
|  199       // and be rendered as HTML or similar. |  200       // and be rendered as HTML or similar. | 
|  200       // Note: Chrome might render the response as HTML if the "Content-Type" |  201       // Note: Chrome might render the response as HTML if the "Content-Type" | 
|  201       // header is missing, invalid or unknown. |  202       // header is missing, invalid or unknown. | 
|  202       // https://chromium.googlesource.com/chromium/src/+/99f41af9/net/http/http
     _util.cc#66 |  203       // https://chromium.googlesource.com/chromium/src/+/99f41af9/net/http/http
     _util.cc#66 | 
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  295         chrome.browserAction.setBadgeBackgroundColor({ |  296         chrome.browserAction.setBadgeBackgroundColor({ | 
|  296           tabId: this._tabId, |  297           tabId: this._tabId, | 
|  297           color: this._changes.badgeColor |  298           color: this._changes.badgeColor | 
|  298         }); |  299         }); | 
|  299       } |  300       } | 
|  300  |  301  | 
|  301       this._changes = null; |  302       this._changes = null; | 
|  302     }, |  303     }, | 
|  303     _queueChanges() |  304     _queueChanges() | 
|  304     { |  305     { | 
|  305       chrome.tabs.get(this._tabId, function() |  306       chrome.tabs.get(this._tabId, () => | 
|  306       { |  307       { | 
|  307         // If the tab is prerendered, chrome.tabs.get() sets |  308         // If the tab is prerendered, chrome.tabs.get() sets | 
|  308         // chrome.runtime.lastError and we have to delay our changes |  309         // chrome.runtime.lastError and we have to delay our changes | 
|  309         // until the currently visible tab is replaced with the |  310         // until the currently visible tab is replaced with the | 
|  310         // prerendered tab. Otherwise chrome.browserAction.set* fails. |  311         // prerendered tab. Otherwise chrome.browserAction.set* fails. | 
|  311         if (chrome.runtime.lastError) |  312         if (chrome.runtime.lastError) | 
|  312         { |  313         { | 
|  313           let onReplaced = (addedTabId, removedTabId) => |  314           let onReplaced = (addedTabId, removedTabId) => | 
|  314           { |  315           { | 
|  315             if (addedTabId == this._tabId) |  316             if (addedTabId == this._tabId) | 
|  316             { |  317             { | 
|  317               chrome.tabs.onReplaced.removeListener(onReplaced); |  318               chrome.tabs.onReplaced.removeListener(onReplaced); | 
|  318               this._applyChanges(); |  319               this._applyChanges(); | 
|  319             } |  320             } | 
|  320           }; |  321           }; | 
|  321           chrome.tabs.onReplaced.addListener(onReplaced); |  322           chrome.tabs.onReplaced.addListener(onReplaced); | 
|  322         } |  323         } | 
|  323         else |  324         else | 
|  324         { |  325         { | 
|  325           this._applyChanges(); |  326           this._applyChanges(); | 
|  326         } |  327         } | 
|  327       }.bind(this)); |  328       }); | 
|  328     }, |  329     }, | 
|  329     _addChange(name, value) |  330     _addChange(name, value) | 
|  330     { |  331     { | 
|  331       if (!this._changes) |  332       if (!this._changes) | 
|  332       { |  333       { | 
|  333         this._changes = {}; |  334         this._changes = {}; | 
|  334         this._queueChanges(); |  335         this._queueChanges(); | 
|  335       } |  336       } | 
|  336  |  337  | 
|  337       this._changes[name] = value; |  338       this._changes[name] = value; | 
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  439  |  440  | 
|  440   /* Web requests */ |  441   /* Web requests */ | 
|  441  |  442  | 
|  442   let framesOfTabs = Object.create(null); |  443   let framesOfTabs = Object.create(null); | 
|  443  |  444  | 
|  444   ext.getFrame = (tabId, frameId) => |  445   ext.getFrame = (tabId, frameId) => | 
|  445   { |  446   { | 
|  446     return (framesOfTabs[tabId] || {})[frameId]; |  447     return (framesOfTabs[tabId] || {})[frameId]; | 
|  447   }; |  448   }; | 
|  448  |  449  | 
|  449   let handlerBehaviorChangedQuota = chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANG
     ED_CALLS_PER_10_MINUTES; |  450   let handlerBehaviorChangedQuota = | 
 |  451     chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; | 
|  450  |  452  | 
|  451   function propagateHandlerBehaviorChange() |  453   function propagateHandlerBehaviorChange() | 
|  452   { |  454   { | 
|  453     // Make sure to not call handlerBehaviorChanged() more often than allowed |  455     // Make sure to not call handlerBehaviorChanged() more often than allowed | 
|  454     // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. |  456     // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. | 
|  455     // Otherwise Chrome notifies the user that this extension is causing issues. |  457     // Otherwise Chrome notifies the user that this extension is causing issues. | 
|  456     if (handlerBehaviorChangedQuota > 0) |  458     if (handlerBehaviorChangedQuota > 0) | 
|  457     { |  459     { | 
|  458       chrome.webNavigation.onBeforeNavigate.removeListener(propagateHandlerBehav
     iorChange); |  460       chrome.webNavigation.onBeforeNavigate.removeListener( | 
 |  461         propagateHandlerBehaviorChange | 
 |  462       ); | 
|  459       chrome.webRequest.handlerBehaviorChanged(); |  463       chrome.webRequest.handlerBehaviorChanged(); | 
|  460  |  464  | 
|  461       handlerBehaviorChangedQuota--; |  465       handlerBehaviorChangedQuota--; | 
|  462       setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); |  466       setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); | 
|  463     } |  467     } | 
|  464   } |  468   } | 
|  465  |  469  | 
|  466   ext.webRequest = { |  470   ext.webRequest = { | 
|  467     onBeforeRequest: new ext._EventTarget(), |  471     onBeforeRequest: new ext._EventTarget(), | 
|  468     handlerBehaviorChanged() |  472     handlerBehaviorChanged() | 
|  469     { |  473     { | 
|  470       // Defer handlerBehaviorChanged() until navigation occurs. |  474       // Defer handlerBehaviorChanged() until navigation occurs. | 
|  471       // There wouldn't be any visible effect when calling it earlier, |  475       // There wouldn't be any visible effect when calling it earlier, | 
|  472       // but it's an expensive operation and that way we avoid to call |  476       // but it's an expensive operation and that way we avoid to call | 
|  473       // it multiple times, if multiple filters are added/removed. |  477       // it multiple times, if multiple filters are added/removed. | 
|  474       let onBeforeNavigate = chrome.webNavigation.onBeforeNavigate; |  478       let {onBeforeNavigate} = chrome.webNavigation; | 
|  475       if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) |  479       if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) | 
|  476         onBeforeNavigate.addListener(propagateHandlerBehaviorChange); |  480         onBeforeNavigate.addListener(propagateHandlerBehaviorChange); | 
|  477     } |  481     } | 
|  478   }; |  482   }; | 
|  479  |  483  | 
|  480   chrome.tabs.query({}, tabs => |  484   chrome.tabs.query({}, tabs => | 
|  481   { |  485   { | 
|  482     tabs.forEach(tab => |  486     tabs.forEach(tab => | 
|  483     { |  487     { | 
|  484       chrome.webNavigation.getAllFrames({tabId: tab.id}, details => |  488       chrome.webNavigation.getAllFrames({tabId: tab.id}, details => | 
|  485       { |  489       { | 
|  486         if (details && details.length > 0) |  490         if (details && details.length > 0) | 
|  487         { |  491         { | 
|  488           let frames = framesOfTabs[tab.id] = Object.create(null); |  492           let frames = framesOfTabs[tab.id] = Object.create(null); | 
|  489  |  493  | 
|  490           for (let i = 0; i < details.length; i++) |  494           for (let i = 0; i < details.length; i++) | 
|  491             frames[details[i].frameId] = {url: new URL(details[i].url), parent: 
     null}; |  495           { | 
 |  496             frames[details[i].frameId] = { | 
 |  497               url: new URL(details[i].url), | 
 |  498               parent: null | 
 |  499             }; | 
 |  500           } | 
|  492  |  501  | 
|  493           for (let i = 0; i < details.length; i++) |  502           for (let i = 0; i < details.length; i++) | 
|  494           { |  503           { | 
|  495             let parentFrameId = details[i].parentFrameId; |  504             let {parentFrameId} = details[i]; | 
|  496  |  505  | 
|  497             if (parentFrameId != -1) |  506             if (parentFrameId != -1) | 
|  498               frames[details[i].frameId].parent = frames[parentFrameId]; |  507               frames[details[i].frameId].parent = frames[parentFrameId]; | 
|  499           } |  508           } | 
|  500         } |  509         } | 
|  501       }); |  510       }); | 
|  502     }); |  511     }); | 
|  503   }); |  512   }); | 
|  504  |  513  | 
|  505   chrome.webRequest.onBeforeRequest.addListener(details => |  514   chrome.webRequest.onBeforeRequest.addListener(details => | 
|  506   { |  515   { | 
|  507     // The high-level code isn't interested in requests that aren't |  516     // The high-level code isn't interested in requests that aren't | 
|  508     // related to a tab or requests loading a top-level document, |  517     // related to a tab or requests loading a top-level document, | 
|  509     // those should never be blocked. |  518     // those should never be blocked. | 
|  510     if (details.tabId == -1 || details.type == "main_frame") |  519     if (details.tabId == -1 || details.type == "main_frame") | 
|  511       return; |  520       return; | 
|  512  |  521  | 
|  513     // We are looking for the frame that contains the element which |  522     // We are looking for the frame that contains the element which | 
|  514     // has triggered this request. For most requests (e.g. images) we |  523     // has triggered this request. For most requests (e.g. images) we | 
|  515     // can just use the request's frame ID, but for subdocument requests |  524     // can just use the request's frame ID, but for subdocument requests | 
|  516     // (e.g. iframes) we must instead use the request's parent frame ID. |  525     // (e.g. iframes) we must instead use the request's parent frame ID. | 
|  517     let frameId; |  526     let {frameId, type} = details; | 
|  518     let requestType; |  527     if (type == "sub_frame") | 
|  519     if (details.type == "sub_frame") |  | 
|  520     { |  528     { | 
|  521       frameId = details.parentFrameId; |  529       frameId = details.parentFrameId; | 
|  522       requestType = "SUBDOCUMENT"; |  530       type = "SUBDOCUMENT"; | 
|  523     } |  | 
|  524     else |  | 
|  525     { |  | 
|  526       frameId = details.frameId; |  | 
|  527       requestType = details.type.toUpperCase(); |  | 
|  528     } |  531     } | 
|  529  |  532  | 
|  530     let frame = ext.getFrame(details.tabId, frameId); |  533     let frame = ext.getFrame(details.tabId, frameId); | 
|  531     if (frame) |  534     if (frame) | 
|  532     { |  535     { | 
|  533       let results = ext.webRequest.onBeforeRequest._dispatch( |  536       let results = ext.webRequest.onBeforeRequest._dispatch( | 
|  534         new URL(details.url), |  537         new URL(details.url), | 
|  535         requestType, |  538         type.toUpperCase(), | 
|  536         new Page({id: details.tabId}), |  539         new Page({id: details.tabId}), | 
|  537         frame |  540         frame | 
|  538       ); |  541       ); | 
|  539  |  542  | 
|  540       if (results.indexOf(false) != -1) |  543       if (results.indexOf(false) != -1) | 
|  541         return {cancel: true}; |  544         return {cancel: true}; | 
|  542     } |  545     } | 
|  543   }, {urls: ["<all_urls>"]}, ["blocking"]); |  546   }, {urls: ["<all_urls>"]}, ["blocking"]); | 
|  544  |  547  | 
|  545  |  548  | 
| (...skipping 19 matching lines...) Expand all  Loading... | 
|  565  |  568  | 
|  566           let frame = frames[rawSender.frameId]; |  569           let frame = frames[rawSender.frameId]; | 
|  567           if (frame) |  570           if (frame) | 
|  568             return frame.parent; |  571             return frame.parent; | 
|  569  |  572  | 
|  570           return frames[0]; |  573           return frames[0]; | 
|  571         } |  574         } | 
|  572       }; |  575       }; | 
|  573     } |  576     } | 
|  574  |  577  | 
|  575     return ext.onMessage._dispatch(message, sender, sendResponse).indexOf(true) 
     != -1; |  578     return ext.onMessage._dispatch( | 
 |  579       message, sender, sendResponse | 
 |  580     ).indexOf(true) != -1; | 
|  576   }); |  581   }); | 
|  577  |  582  | 
|  578  |  583  | 
|  579   /* Storage */ |  584   /* Storage */ | 
|  580  |  585  | 
|  581   ext.storage = { |  586   ext.storage = { | 
|  582     get(keys, callback) |  587     get(keys, callback) | 
|  583     { |  588     { | 
|  584       chrome.storage.local.get(keys, callback); |  589       chrome.storage.local.get(keys, callback); | 
|  585     }, |  590     }, | 
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  668   /* Windows */ |  673   /* Windows */ | 
|  669   ext.windows = { |  674   ext.windows = { | 
|  670     create(createData, callback) |  675     create(createData, callback) | 
|  671     { |  676     { | 
|  672       chrome.windows.create(createData, createdWindow => |  677       chrome.windows.create(createData, createdWindow => | 
|  673       { |  678       { | 
|  674         afterTabLoaded(callback)(createdWindow.tabs[0]); |  679         afterTabLoaded(callback)(createdWindow.tabs[0]); | 
|  675       }); |  680       }); | 
|  676     } |  681     } | 
|  677   }; |  682   }; | 
|  678 } |  683 }()); | 
| OLD | NEW |