11'use strict' ;
22
3- let BMU = new ( function ( ) {
3+ const BMU = function ( ) {
44
55 this . scannedBookmarks = null ;
66
@@ -27,7 +27,7 @@ let BMU = new (function(){
2727 title : new this . State ( ) ,
2828 clear : function ( ) { this . url . clear ( ) && this . title . clear ( ) }
2929 }
30- }
30+ } ;
3131
3232 this . debug = true ;
3333
@@ -173,7 +173,7 @@ let BMU = new (function(){
173173 case "update" :
174174 this . print ( "received update request" ) ;
175175 sendResponse ( result ) ;
176- result . ok && this . update ( ) ;
176+ result . ok && this . update ( request . excludes , request . allowUrlFixup ) ;
177177 break ;
178178 case "list" :
179179 this . print ( "received scanned list request" ) ;
@@ -195,7 +195,7 @@ let BMU = new (function(){
195195 this . createBookmarkList = async function ( ) {
196196
197197 const BM = function ( bm , r , includeReplacement ) {
198- const o = { } ;
198+ const o = { id : bm . id } ;
199199 if ( r . url [ 0 ] && typeof r . url [ 1 ] === "string" ) {
200200 o . url = { "match" : bm . url } ;
201201 if ( includeReplacement ) {
@@ -218,7 +218,7 @@ let BMU = new (function(){
218218 let bookmarks = this . scannedBookmarks ;
219219
220220 let list = [ ] ;
221- const END = Math . min ( 100 , bookmarks . length ) ;
221+ const END = bookmarks . length ;
222222
223223 const REPLACER = this . getReplacers ( ) ;
224224
@@ -232,7 +232,11 @@ let BMU = new (function(){
232232 list . push ( { note :`--- and ${ bookmarks . length - END } more ---` } ) ;
233233 }
234234 this . operations . running = null ;
235- browser . runtime . sendMessage ( { type :"list" , list :list } ) ;
235+ browser . runtime . sendMessage ( {
236+ type :"list" ,
237+ operation :this . operations . type ,
238+ list :list
239+ } ) ;
236240 } ;
237241
238242 this . parseDomain = function ( url ) {
@@ -247,7 +251,11 @@ let BMU = new (function(){
247251
248252 this . isBookmarkATarget = function ( node , ref ) {
249253 let rv = false ;
250- let queries = { url :this . operations . operands . url . from , title :this . operations . operands . title . from } ; // This has been set earlier by this.isOpValid()
254+ let queries = {
255+ url : this . operations . operands . url . from ,
256+ title : this . operations . operands . title . from
257+ } ; // This has been set earlier by this.isOpValid()
258+
251259 switch ( ref . options . type ) {
252260 case "protocol" :
253261 return ( / ^ h t t p : / ) . test ( node . url ) && ( queries . url === null || this . parseDomain ( node . url ) . endsWith ( queries . url ) )
@@ -299,7 +307,12 @@ let BMU = new (function(){
299307 )
300308 . finally ( ( ) => {
301309 this . operations . running = null ;
302- browser . runtime . sendMessage ( { type :"scan" , success :! ( this . scannedBookmarks === null ) , length :this . scannedBookmarks ?this . scannedBookmarks . length :0 , domain :options . fromDomain } ) ;
310+ browser . runtime . sendMessage ( {
311+ type : "scan" ,
312+ success : ! ( this . scannedBookmarks === null ) ,
313+ length : this . scannedBookmarks ?this . scannedBookmarks . length :0 ,
314+ domain : options . fromDomain
315+ } ) ;
303316 } )
304317 }
305318
@@ -316,10 +329,19 @@ let BMU = new (function(){
316329 }
317330 }
318331
332+
319333 this . isValidURL = function ( url , hasNoBackslash ) {
320334 let rv = true ;
335+
336+ function tryMakeUrl ( base ) {
337+ try {
338+ return new URL ( base )
339+ } catch ( e ) {
340+ return null
341+ }
342+ }
321343 try {
322- let d = new URL ( url ) ;
344+ let d = tryMakeUrl ( url ) || tryMakeUrl ( decodeURIComponent ( url ) ) ;
323345 rv = ! d . host . startsWith ( "." )
324346 && ! d . host . endsWith ( "." )
325347 && d . host . indexOf ( ".." ) === - 1
@@ -348,8 +370,15 @@ let BMU = new (function(){
348370 }
349371 return rv
350372 }
351-
352- this . update = async function ( ) {
373+ this . reformatUrl = function ( url , index ) {
374+ try {
375+ return decodeURIComponent ( url . slice ( 0 , index ) ) + url . slice ( index )
376+ } catch ( e ) {
377+ // this will intentionally cause bookmarks.update() to fail
378+ return null
379+ }
380+ }
381+ this . update = async function ( excludes , allowfixup ) {
353382
354383 if ( this . scannedBookmarks === null ) {
355384 return
@@ -365,11 +394,15 @@ let BMU = new (function(){
365394 const CHANGES = { } ;
366395 CHANGES . url = replacer . url [ 0 ] && ( this . operations . type === "regexp" || replacer . url [ 1 ] ) ;
367396 CHANGES . title = this . operations . type === "regexp" && replacer . title [ 0 ] && ( typeof replacer . title [ 1 ] === "string" ) ;
368-
397+ let exclusions = 0 ;
369398 let failures = [ ] ;
370399 let success = false ;
371400 for ( let bm of this . scannedBookmarks ) {
372-
401+ if ( excludes . includes ( bm . id ) ) {
402+ this . operations . progress . current ++ ;
403+ exclusions ++ ;
404+ continue
405+ }
373406 let newProps = { } ;
374407 let failedURL ;
375408 if ( CHANGES . url ) {
@@ -385,11 +418,25 @@ let BMU = new (function(){
385418
386419 if ( newProps . url || newProps . title ) {
387420 const ID = bm . id ;
421+ // This path should handle an outcome where the resulting url is in
422+ // in encoded form and thus invalid. If such a scenario were to happen
423+ // then it's very likely that the url does not have a ":" in it
424+ // so use that as a simple detecting mechanism. Moreso, should
425+ // such a scenario occur, then it is likely that the correct url
426+ // was part of a query parameter
427+ if ( allowfixup && newProps . url . indexOf ( ":" ) === - 1 ) {
428+ let queryIndex = newProps . url . indexOf ( "?" ) ;
429+ if ( queryIndex > - 1 ) {
430+ newProps . url = this . reformatUrl ( newProps . url , queryIndex ) ;
431+ } else {
432+ newProps . url = this . reformatUrl ( newProps . url , newProps . url . length ) ;
433+ }
434+ }
388435 let updating = browser . bookmarks . update ( ID , newProps ) ;
389436
390437 // This error should only happen if the bookmark to be updated is no longer available when the update is being run but it was available when scanning
391438 updating
392- . catch ( ( e ) => ( failures . push ( { error :`invalid id: ${ ID } ` } ) , true ) )
439+ . catch ( ( e ) => ( failures . push ( { error :e . message } ) , true ) )
393440 . finally ( ( ) => ( this . operations . progress . current ++ ) ) ;
394441
395442 bookmarkPromises . push ( updating ) ;
@@ -399,12 +446,17 @@ let BMU = new (function(){
399446 this . operations . progress . current ++
400447 }
401448 }
402- Promise . all ( bookmarkPromises )
449+ Promise . allSettled ( bookmarkPromises )
403450 . then ( ( ) => { success = true } )
404451 // If we end up in this catch it implies error in the script
405452 . catch ( ( e ) => { console . error ( e ) } )
406453 . then ( ( ) => {
407- browser . runtime . sendMessage ( { type :"update" , success :( success && ! failures . length ) , length :this . operations . progress . current , failures :failures . length ?failures :null } ) ;
454+ browser . runtime . sendMessage ( {
455+ type : "update" ,
456+ success : ( success && ! failures . length ) ,
457+ length : this . operations . progress . current - exclusions ,
458+ failures : failures . length ?failures :null
459+ } ) ;
408460 this . operations . running = null ;
409461 // reset() takes one "force" argument to fully reset status so we can recover from hard errors
410462 // note - success will be true if bookmark couldn't be updated due to no matching id
@@ -414,11 +466,16 @@ let BMU = new (function(){
414466
415467 browser . runtime . onMessage . addListener ( this . messageHandler . bind ( this ) ) ;
416468
417- } ) ( ) ;
469+ } ;
470+
471+ let engine = null ;
418472
419473async function switchToOrOpenTab ( ) {
420- let views = await browser . extension . getViews ( { type :"tab" } )
474+ let views = await browser . extension . getViews ( { type :"tab" } ) ;
421475 if ( ! views . length ) {
476+ if ( ! engine ) {
477+ engine = new BMU ( ) ;
478+ }
422479 browser . tabs . create ( { url :"ui.html" } )
423480 } else {
424481 let aTab = await views [ 0 ] . browser . tabs . getCurrent ( )
0 commit comments