diff options
Diffstat (limited to 'chromium/chrome/browser/resources/cryptotoken')
13 files changed, 179 insertions, 69 deletions
diff --git a/chromium/chrome/browser/resources/cryptotoken/appid.js b/chromium/chrome/browser/resources/cryptotoken/appid.js index dddf08377f0..5e2cb7d0fb3 100644 --- a/chromium/chrome/browser/resources/cryptotoken/appid.js +++ b/chromium/chrome/browser/resources/cryptotoken/appid.js @@ -16,25 +16,20 @@ function getOriginsFromJson(text) { try { var urls, i; var appIdData = JSON.parse(text); - if (Array.isArray(appIdData)) { - // Older format where it is a simple list of facets - urls = appIdData; - } else { - var trustedFacets = appIdData['trustedFacets']; - if (trustedFacets) { - var versionBlock; - for (i = 0; versionBlock = trustedFacets[i]; i++) { - if (versionBlock['version'] && - versionBlock['version']['major'] == 1 && - versionBlock['version']['minor'] == 0) { - urls = versionBlock['ids']; - break; - } + var trustedFacets = appIdData['trustedFacets']; + if (trustedFacets) { + var versionBlock; + for (i = 0; versionBlock = trustedFacets[i]; i++) { + if (versionBlock['version'] && + versionBlock['version']['major'] == 1 && + versionBlock['version']['minor'] == 0) { + urls = versionBlock['ids']; + break; } } - if (typeof urls == 'undefined') { - throw Error('Could not find trustedFacets for version 1.0'); - } + } + if (typeof urls == 'undefined') { + throw Error('Could not find trustedFacets for version 1.0'); } var origins = {}; var url; @@ -207,7 +202,7 @@ XhrAppIdChecker.prototype.fetchAllowedOriginsForAppId_ = function(appId) { return Promise.resolve([]); } - if (appId.startsWith('http://') && !this.allowHttp_) { + if (appId.indexOf('http://') == 0 && !this.allowHttp_) { console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested')); return Promise.resolve([]); } diff --git a/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js b/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js index a7bb72c1756..165102abde9 100644 --- a/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js +++ b/chromium/chrome/browser/resources/cryptotoken/devicestatuscodes.js @@ -1,4 +1,4 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. +// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/chromium/chrome/browser/resources/cryptotoken/enroller.js b/chromium/chrome/browser/resources/cryptotoken/enroller.js index 623337373d1..42b7c205deb 100644 --- a/chromium/chrome/browser/resources/cryptotoken/enroller.js +++ b/chromium/chrome/browser/resources/cryptotoken/enroller.js @@ -49,7 +49,7 @@ function handleU2fEnrollRequest(messageSender, request, sendResponse) { sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST}); return null; } - if (sender.origin.startsWith('http://') && !HTTP_ORIGINS_ALLOWED) { + if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) { sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST}); return null; } @@ -261,7 +261,7 @@ function Enroller(timer, sender, errorCb, successCb, opt_logMsgUrl) { // what they get.) /** @private {boolean} */ this.allowHttp_ = - this.sender_.origin ? this.sender_.origin.startsWith('http://') : false; + this.sender_.origin ? this.sender_.origin.indexOf('http://') == 0 : false; /** @private {Closeable} */ this.handler_ = null; } @@ -310,6 +310,9 @@ Enroller.prototype.approveOrigin_ = function() { if (!result) { // Origin not approved: rather than give an explicit indication to // the web page, let a timeout occur. + // NOTE: if you are looking at this in a debugger, this line will + // always be false since the origin of the debugger is different + // than origin of requesting page if (self.timer_.expired()) { self.notifyTimeout_(); return; diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubbies.js b/chromium/chrome/browser/resources/cryptotoken/gnubbies.js index 86eb6e6a716..8424b393a8a 100644 --- a/chromium/chrome/browser/resources/cryptotoken/gnubbies.js +++ b/chromium/chrome/browser/resources/cryptotoken/gnubbies.js @@ -9,7 +9,17 @@ /** * @typedef {{ + * vendorId: (number|undefined), + * productId: (number|undefined), + * usagePage: (number|undefined) + * }} + */ +var GnubbyEnumerationFilter; + +/** + * @typedef {{ * namespace: string, + * enumeratedBy: (GnubbyEnumerationFilter|undefined), * device: number * }} */ diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubby.js b/chromium/chrome/browser/resources/cryptotoken/gnubby.js index 197f9fb33d8..b0493cba0c4 100644 --- a/chromium/chrome/browser/resources/cryptotoken/gnubby.js +++ b/chromium/chrome/browser/resources/cryptotoken/gnubby.js @@ -111,6 +111,9 @@ Gnubby.prototype.open = function(which, opt_type, opt_cb, opt_caller) { return; } self.dev = device; + if (self.closeHook_) { + self.dev.setDestroyHook(self.closeHook_); + } cb(rc); }); } @@ -119,7 +122,12 @@ Gnubby.prototype.open = function(which, opt_type, opt_cb, opt_caller) { setCid(which); self.which = which; Gnubby.gnubbies_.addClient(which, self, function(rc, device) { - self.dev = device; + if (!rc) { + self.dev = device; + if (self.closeHook_) { + self.dev.setDestroyHook(self.closeHook_); + } + } cb(rc); }); } else { @@ -186,6 +194,15 @@ Gnubby.prototype.closeWhenIdle = function(cb) { }; /** + * Sets a callback that will get called when this gnubby is closed. + * @param {function() : ?Promise} cb Called back when closed. Callback + * may yield a promise that resolves when the close hook completes. + */ +Gnubby.prototype.setCloseHook = function(cb) { + this.closeHook_ = cb; +}; + +/** * Close and notify every caller that it is now closed. * @private */ @@ -236,6 +253,13 @@ Gnubby.prototype.receivedFrame = function(frame) { }; /** + * @return {number|undefined} The last read error seen by this device. + */ +Gnubby.prototype.getLastReadError = function() { + return this.lastReadError_; +}; + +/** * @return {ArrayBuffer|Uint8Array} oldest received frame. Throw if none. * @private */ @@ -276,6 +300,7 @@ Gnubby.prototype.read_ = function(cmd, timeout, cb) { window.clearTimeout(tid); tid = null; } + self.lastReadError_ = /** @private {number|undefined} */ (a); var c = callback; if (c) { callback = null; diff --git a/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js b/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js index 3030a375ab2..fffa19ceb99 100644 --- a/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js +++ b/chromium/chrome/browser/resources/cryptotoken/gnubbydevice.js @@ -30,6 +30,8 @@ GnubbyDevice.CMD_INIT = 0x86; GnubbyDevice.CMD_PROMPT = 0x87; /** Send device identification wink */ GnubbyDevice.CMD_WINK = 0x88; +/** BLE UID read/set */ +GnubbyDevice.CMD_BLE_UID = 0xb5; /** USB test */ GnubbyDevice.CMD_USB_TEST = 0xb9; /** Device Firmware Upgrade */ @@ -85,6 +87,13 @@ GnubbyDevice.NOPERMISSION = 666; GnubbyDevice.prototype.destroy = function() {}; /** + * Sets a callback that will get called when this device instance is destroyed. + * @param {function() : ?Promise} cb Called back when closed. Callback may + * yield a promise that resolves when the close hook completes. + */ +GnubbyDevice.prototype.setDestroyHook = function(cb) {}; + +/** * Register a client for this gnubby. * @param {*} who The client. */ diff --git a/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js b/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js index 80c40c00b16..ecd83df1637 100644 --- a/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js +++ b/chromium/chrome/browser/resources/cryptotoken/hidgnubbydevice.js @@ -40,6 +40,18 @@ HidGnubbyDevice.NAMESPACE = 'hid'; HidGnubbyDevice.prototype.destroy = function() { if (!this.dev) return; // Already dead. + function closeLowLevelDevice(dev) { + chrome.hid.disconnect(dev.connectionId, function() { + if (chrome.runtime.lastError) { + console.warn(UTIL_fmt('Device ' + dev.connectionId + + ' couldn\'t be disconnected:')); + console.warn(UTIL_fmt(chrome.runtime.lastError.message)); + return; + } + console.log(UTIL_fmt('Device ' + dev.connectionId + ' closed')); + }); + } + this.gnubbies_.removeOpenDevice( {namespace: HidGnubbyDevice.NAMESPACE, device: this.id}); this.closing = true; @@ -69,16 +81,29 @@ HidGnubbyDevice.prototype.destroy = function() { var dev = this.dev; this.dev = null; + var reallyCloseDevice = closeLowLevelDevice.bind(null, dev); - chrome.hid.disconnect(dev.connectionId, function() { - if (chrome.runtime.lastError) { - console.warn(UTIL_fmt('Device ' + dev.connectionId + - ' couldn\'t be disconnected:')); - console.warn(UTIL_fmt(chrome.runtime.lastError.message)); + if (this.destroyHook_) { + var p = this.destroyHook_(); + if (!p) { + reallyCloseDevice(); return; } - console.log(UTIL_fmt('Device ' + dev.connectionId + ' closed')); - }); + // When this method returns, a device reference may still be held, until the + // promise completes. + p.then(reallyCloseDevice); + } else { + reallyCloseDevice(); + } +}; + +/** + * Sets a callback that will get called when this device instance is destroyed. + * @param {function() : ?Promise} cb Called back when closed. Callback may + * yield a promise that resolves when the close hook completes. + */ +HidGnubbyDevice.prototype.setDestroyHook = function(cb) { + this.destroyHook_ = cb; }; /** @@ -411,7 +436,7 @@ HidGnubbyDevice.prototype.writePump_ = function() { * @const */ HidGnubbyDevice.HID_VID_PIDS = [ - {'vendorId': 4176, 'productId': 512} // Google-specific Yubico HID + {'vendorId': 4176, 'productId': 512} // Google-specific Yubico HID ]; /** @@ -427,20 +452,20 @@ HidGnubbyDevice.enumerate = function(cb, opt_type) { var numEnumerated = 0; var allDevs = []; - function enumerated(f1d0Enumerated, devs) { + function enumerated(filter, devs) { // Don't double-add a device; it'll just confuse things. // We assume the various calls to getDevices() return from the same // deviceId pool. for (var i = 0; i < devs.length; i++) { var dev = devs[i]; - dev.f1d0Only = f1d0Enumerated; + dev.enumeratedBy = filter; // Unfortunately indexOf is not usable, since the two calls produce // different objects. Compare their deviceIds instead. var found = false; for (var j = 0; j < allDevs.length; j++) { if (allDevs[j].deviceId == dev.deviceId) { found = true; - allDevs[j].f1d0Only &= f1d0Enumerated; + allDevs[j].enumeratedBy = filter; break; } } @@ -456,11 +481,12 @@ HidGnubbyDevice.enumerate = function(cb, opt_type) { // Pass 1: usagePage-based enumeration, for FIDO U2F devices. If non-FIDO // devices are asked for, "implement" this pass by providing it the empty // list. (enumerated requires that it's called once per pass.) + var f1d0Filter = {usagePage: 0xf1d0}; if (opt_type == GnubbyEnumerationTypes.VID_PID) { - enumerated(true, []); + enumerated(f1d0Filter, []); } else { - chrome.hid.getDevices({filters: [{usagePage: 0xf1d0}]}, - enumerated.bind(null, true)); + chrome.hid.getDevices({filters: [f1d0Filter]}, + enumerated.bind(null, f1d0Filter)); } // Pass 2: vid/pid-based enumeration, for legacy devices. If FIDO devices // are asked for, "implement" this pass by providing it the empty list. @@ -468,8 +494,8 @@ HidGnubbyDevice.enumerate = function(cb, opt_type) { enumerated(false, []); } else { for (var i = 0; i < HidGnubbyDevice.HID_VID_PIDS.length; i++) { - var dev = HidGnubbyDevice.HID_VID_PIDS[i]; - chrome.hid.getDevices({filters: [dev]}, enumerated.bind(null, false)); + var vidPid = HidGnubbyDevice.HID_VID_PIDS[i]; + chrome.hid.getDevices({filters: [vidPid]}, enumerated.bind(null, vidPid)); } } }; @@ -507,6 +533,7 @@ HidGnubbyDevice.deviceToDeviceId = function(dev) { var hidDev = /** @type {!chrome.hid.HidDeviceInfo} */ (dev); var deviceId = { namespace: HidGnubbyDevice.NAMESPACE, + enumeratedBy: hidDev.enumeratedBy, device: hidDev.deviceId }; return deviceId; diff --git a/chromium/chrome/browser/resources/cryptotoken/manifest.json b/chromium/chrome/browser/resources/cryptotoken/manifest.json index 6e4c46df243..b0dbce64b39 100644 --- a/chromium/chrome/browser/resources/cryptotoken/manifest.json +++ b/chromium/chrome/browser/resources/cryptotoken/manifest.json @@ -1,7 +1,7 @@ { "name": "CryptoTokenExtension", "description": "CryptoToken Component Extension", - "version": "0.9.38", + "version": "0.9.46", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq7zRobvA+AVlvNqkHSSVhh1sEWsHSqz4oR/XptkDe/Cz3+gW9ZGumZ20NCHjaac8j1iiesdigp8B1LJsd/2WWv2Dbnto4f8GrQ5MVphKyQ9WJHwejEHN2K4vzrTcwaXqv5BSTXwxlxS/mXCmXskTfryKTLuYrcHEWK8fCHb+0gvr8b/kvsi75A1aMmb6nUnFJvETmCkOCPNX5CHTdy634Ts/x0fLhRuPlahk63rdf7agxQv5viVjQFk+tbgv6aa9kdSd11Js/RZ9yZjrFgHOBWgP4jTBqud4+HUglrzu8qynFipyNRLCZsaxhm+NItTyNgesxLdxZcwOz56KD1Q4IQIDAQAB", "manifest_version": 2, "permissions": [ diff --git a/chromium/chrome/browser/resources/cryptotoken/signer.js b/chromium/chrome/browser/resources/cryptotoken/signer.js index 3605454208f..6a0fe53c09b 100644 --- a/chromium/chrome/browser/resources/cryptotoken/signer.js +++ b/chromium/chrome/browser/resources/cryptotoken/signer.js @@ -47,7 +47,7 @@ function handleU2fSignRequest(messageSender, request, sendResponse) { sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST}); return null; } - if (sender.origin.startsWith('http://') && !HTTP_ORIGINS_ALLOWED) { + if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) { sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST}); return null; } @@ -319,7 +319,7 @@ function Signer(timer, sender, errorCb, successCb, opt_logMsgUrl) { // what they get.) /** @private {boolean} */ this.allowHttp_ = this.sender_.origin ? - this.sender_.origin.startsWith('http://') : false; + this.sender_.origin.indexOf('http://') == 0 : false; /** @private {Closeable} */ this.handler_ = null; } diff --git a/chromium/chrome/browser/resources/cryptotoken/singlesigner.js b/chromium/chrome/browser/resources/cryptotoken/singlesigner.js index b76cc991fc2..9d535058620 100644 --- a/chromium/chrome/browser/resources/cryptotoken/singlesigner.js +++ b/chromium/chrome/browser/resources/cryptotoken/singlesigner.js @@ -463,12 +463,13 @@ SingleGnubbySigner.prototype.goToError_ = function(code, opt_warn) { var logFn = opt_warn ? console.warn.bind(console) : console.log.bind(console); logFn(UTIL_fmt('failed (' + code.toString(16) + ')')); var result = { code: code }; - if (!this.forEnroll_ && code == DeviceStatusCodes.WRONG_DATA_STATUS) { - // When a device yields WRONG_DATA to all sign challenges, and this is a - // sign request, we don't want to yield to the web page that it's not - // enrolled just yet: we want the user to tap the device first. We'll - // report the gnubby to the caller and let it close it instead of closing - // it here. + if (!this.forEnroll_ && + SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(code)) { + // When a device yields an idempotent bad key handle error to all sign + // challenges, and this is a sign request, we don't want to yield to the + // web page that it's not enrolled just yet: we want the user to tap the + // device first. We'll report the gnubby to the caller and let it close it + // instead of closing it here. result.gnubby = this.gnubby_; } else { // Since this gnubby can no longer produce a useful result, go ahead and diff --git a/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js b/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js index e37f9b31e10..595d3736033 100644 --- a/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js +++ b/chromium/chrome/browser/resources/cryptotoken/usbgnubbydevice.js @@ -44,6 +44,27 @@ UsbGnubbyDevice.NAMESPACE = 'usb'; /** Destroys this low-level device instance. */ UsbGnubbyDevice.prototype.destroy = function() { + function closeLowLevelDevice(dev) { + chrome.usb.releaseInterface(dev, 0, function() { + if (chrome.runtime.lastError) { + console.warn(UTIL_fmt('Device ' + dev.handle + + ' couldn\'t be released:')); + console.warn(UTIL_fmt(chrome.runtime.lastError.message)); + return; + } + console.log(UTIL_fmt('Device ' + dev.handle + ' released')); + chrome.usb.closeDevice(dev, function() { + if (chrome.runtime.lastError) { + console.warn(UTIL_fmt('Device ' + dev.handle + + ' couldn\'t be closed:')); + console.warn(UTIL_fmt(chrome.runtime.lastError.message)); + return; + } + console.log(UTIL_fmt('Device ' + dev.handle + ' closed')); + }); + }); + } + if (!this.dev) return; // Already dead. this.gnubbies_.removeOpenDevice( @@ -75,25 +96,27 @@ UsbGnubbyDevice.prototype.destroy = function() { var dev = this.dev; this.dev = null; + var reallyCloseDevice = closeLowLevelDevice.bind(null, dev); - chrome.usb.releaseInterface(dev, 0, function() { - if (chrome.runtime.lastError) { - console.warn(UTIL_fmt('Device ' + dev.handle + - ' couldn\'t be released:')); - console.warn(UTIL_fmt(chrome.runtime.lastError.message)); + if (this.destroyHook_) { + var p = this.destroyHook_(); + if (!p) { + reallyCloseDevice(); return; } - console.log(UTIL_fmt('Device ' + dev.handle + ' released')); - chrome.usb.closeDevice(dev, function() { - if (chrome.runtime.lastError) { - console.warn(UTIL_fmt('Device ' + dev.handle + - ' couldn\'t be closed:')); - console.warn(UTIL_fmt(chrome.runtime.lastError.message)); - return; - } - console.log(UTIL_fmt('Device ' + dev.handle + ' closed')); - }); - }); + p.then(reallyCloseDevice); + } else { + reallyCloseDevice(); + } +}; + +/** + * Sets a callback that will get called when this device instance is destroyed. + * @param {function() : ?Promise} cb Called back when closed. Callback may + * yield a promise that resolves when the close hook completes. + */ +UsbGnubbyDevice.prototype.setDestroyHook = function(cb) { + this.destroyHook_ = cb; }; /** @@ -391,7 +414,7 @@ UsbGnubbyDevice.prototype.queueCommand = function(cid, cmd, data) { * @const */ UsbGnubbyDevice.WINUSB_VID_PIDS = [ - {'vendorId': 4176, 'productId': 529} // Yubico WinUSB + {'vendorId': 4176, 'productId': 529} // Yubico WinUSB ]; /** @@ -409,15 +432,21 @@ UsbGnubbyDevice.enumerate = function(cb, opt_type) { var numEnumerated = 0; var allDevs = []; - function enumerated(devs) { - allDevs = allDevs.concat(devs); + function enumerated(vidPid, devs) { + if (devs) { + for (var i = 0; i < devs.length; i++) { + devs[i].enumeratedBy = vidPid; + } + allDevs = allDevs.concat(devs); + } if (++numEnumerated == UsbGnubbyDevice.WINUSB_VID_PIDS.length) { cb(allDevs); } } for (var i = 0; i < UsbGnubbyDevice.WINUSB_VID_PIDS.length; i++) { - chrome.usb.getDevices(UsbGnubbyDevice.WINUSB_VID_PIDS[i], enumerated); + var vidPid = UsbGnubbyDevice.WINUSB_VID_PIDS[i]; + chrome.usb.getDevices(vidPid, enumerated.bind(null, vidPid)); } }; @@ -505,6 +534,10 @@ UsbGnubbyDevice.open = function(gnubbies, which, dev, cb) { cb(-GnubbyDevice.BUSY); return; } + // Restore the enumeratedBy value, if we had it. + if (enumeratedBy) { + dev.enumeratedBy = enumeratedBy; + } var gnubby = new UsbGnubbyDevice(gnubbies, nonNullHandle, which, inEndpoint, outEndpoint); cb(-GnubbyDevice.OK, gnubby); @@ -512,10 +545,15 @@ UsbGnubbyDevice.open = function(gnubbies, which, dev, cb) { }); } + var enumeratedBy = dev.enumeratedBy; + if (UsbGnubbyDevice.runningOnCrOS === undefined) { UsbGnubbyDevice.runningOnCrOS = (window.navigator.appVersion.indexOf('; CrOS ') != -1); } + // dev contains an enumeratedBy value, which we need to strip prior to + // calling Chrome APIs with it. + delete dev.enumeratedBy; if (UsbGnubbyDevice.runningOnCrOS) { chrome.usb.requestAccess(dev, 0, function(success) { // Even though the argument to requestAccess is a chrome.usb.Device, the @@ -539,6 +577,7 @@ UsbGnubbyDevice.deviceToDeviceId = function(dev) { var usbDev = /** @type {!chrome.usb.Device} */ (dev); var deviceId = { namespace: UsbGnubbyDevice.NAMESPACE, + enumeratedBy: dev.enumeratedBy, device: usbDev.device }; return deviceId; diff --git a/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js b/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js index d06b529708e..57d23583043 100644 --- a/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js +++ b/chromium/chrome/browser/resources/cryptotoken/usbsignhandler.js @@ -96,7 +96,8 @@ UsbSignHandler.prototype.signerFoundGnubby_ = var challenge = signResult['challenge']; var info = new Uint8Array(signResult['info']); this.notifySuccess_(gnubby, challenge, info); - } else if (signResult.code == DeviceStatusCodes.WRONG_DATA_STATUS) { + } else if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle( + signResult.code)) { var gnubby = signResult['gnubby']; this.notEnrolledGnubbies_.push(gnubby); this.sendBogusEnroll_(gnubby); diff --git a/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js b/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js index 0b5817cf2bc..329a6ab4074 100644 --- a/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js +++ b/chromium/chrome/browser/resources/cryptotoken/webrequestsender.js @@ -88,7 +88,7 @@ function getTabIdWhenPossible(sender) { resolve(true); }, function() { // Didn't match? Check if the debugger is open. - if (!tab.url.startsWith('chrome-devtools://')) { + if (tab.url.indexOf('chrome-devtools://') != 0) { reject(false); return; } |